Skip to content

Commit

Permalink
HHH-18604 Add json_array_insert
Browse files Browse the repository at this point in the history
  • Loading branch information
beikov committed Sep 18, 2024
1 parent 8dfc2a5 commit 0e5846b
Show file tree
Hide file tree
Showing 20 changed files with 439 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -1643,6 +1643,7 @@ it is necessary to enable the `hibernate.query.hql.json_functions_enabled` confi
| `json_remove()` | Removes a value by JSON path within a JSON document
| `json_mergepatch()` | Merges JSON documents by performing an https://tools.ietf.org/html/rfc7396[RFC 7396] compliant merge
| `json_array_append()` | Appends to a JSON array of a JSON document by JSON path
| `json_array_insert()` | Inserts a value by JSON path to a JSON array within a JSON document
|===
Expand Down Expand Up @@ -2134,6 +2135,29 @@ include::{json-example-dir-hql}/JsonArrayAppendTest.java[tags=hql-json-array-app
WARNING: SAP HANA, DB2, H2 and HSQLDB do not support this function.
[[hql-json-array-insert-function]]
===== `json_array_insert()`
Inserts a value by JSON path to a JSON array within a JSON document.
The function takes 3 arguments, the json document, the json path and the value to append.
Although the exact behavior is database dependent, usually an error will be triggered if
the JSON path does not end with an array index access i.e. `$.a[0]`.
The zero based array index represents the position at which an element should be inserted in an array.
If the JSON path without the index does not resolve to a JSON array within the JSON document,
the document is not changed.
[[hql-json-array-insert-example]]
====
[source, java, indent=0]
----
include::{json-example-dir-hql}/JsonArrayInsertTest.java[tags=hql-json-array-insert-example]
----
====
WARNING: SAP HANA, DB2, H2 and HSQLDB do not support this function.
[[hql-user-defined-functions]]
==== Native and user-defined functions
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -514,6 +514,7 @@ public void initializeFunctionRegistry(FunctionContributions functionContributio
functionFactory.jsonInsert_postgresql();
functionFactory.jsonMergepatch_postgresql();
functionFactory.jsonArrayAppend_postgresql();
functionFactory.jsonArrayInsert_postgresql();

// Postgres uses # instead of ^ for XOR
functionContributions.getFunctionRegistry().patternDescriptorBuilder( "bitxor", "(?1#?2)" )
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -666,6 +666,7 @@ public void initializeFunctionRegistry(FunctionContributions functionContributio
functionFactory.jsonInsert_mysql();
functionFactory.jsonMergepatch_mysql();
functionFactory.jsonArrayAppend_mysql();
functionFactory.jsonArrayInsert_mysql();
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,7 @@ public void initializeFunctionRegistry(FunctionContributions functionContributio
functionFactory.jsonInsert_oracle();
functionFactory.jsonMergepatch_oracle();
functionFactory.jsonArrayAppend_oracle();
functionFactory.jsonArrayInsert_oracle();
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -664,6 +664,7 @@ public void initializeFunctionRegistry(FunctionContributions functionContributio
functionFactory.jsonInsert_postgresql();
functionFactory.jsonMergepatch_postgresql();
functionFactory.jsonArrayAppend_postgresql();
functionFactory.jsonArrayInsert_postgresql();

if ( getVersion().isSameOrAfter( 9, 4 ) ) {
functionFactory.makeDateTimeTimestamp();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,7 @@ public void initializeFunctionRegistry(FunctionContributions functionContributio
functionFactory.jsonReplace_sqlserver();
functionFactory.jsonInsert_sqlserver();
functionFactory.jsonArrayAppend_sqlserver();
functionFactory.jsonArrayInsert_sqlserver();
}
if ( getVersion().isSameOrAfter( 14 ) ) {
functionFactory.listagg_stringAggWithinGroup( "varchar(max)" );
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -481,6 +481,7 @@ public void initializeFunctionRegistry(FunctionContributions functionContributio
functionFactory.jsonInsert_postgresql();
functionFactory.jsonMergepatch_postgresql();
functionFactory.jsonArrayAppend_postgresql();
functionFactory.jsonArrayInsert_postgresql();

// Postgres uses # instead of ^ for XOR
functionContributions.getFunctionRegistry().patternDescriptorBuilder( "bitxor", "(?1#?2)" )
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -651,6 +651,7 @@ public void initializeFunctionRegistry(FunctionContributions functionContributio
functionFactory.jsonInsert_mysql();
functionFactory.jsonMergepatch_mysql();
functionFactory.jsonArrayAppend_mysql();
functionFactory.jsonArrayInsert_mysql();
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -414,6 +414,7 @@ public void initializeFunctionRegistry(FunctionContributions functionContributio
functionFactory.jsonInsert_oracle();
functionFactory.jsonMergepatch_oracle();
functionFactory.jsonArrayAppend_oracle();
functionFactory.jsonArrayInsert_oracle();
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -625,6 +625,7 @@ public void initializeFunctionRegistry(FunctionContributions functionContributio
functionFactory.jsonInsert_postgresql();
functionFactory.jsonMergepatch_postgresql();
functionFactory.jsonArrayAppend_postgresql();
functionFactory.jsonArrayInsert_postgresql();

functionFactory.makeDateTimeTimestamp();
// Note that PostgreSQL doesn't support the OVER clause for ordered set-aggregate functions
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -429,6 +429,7 @@ public void initializeFunctionRegistry(FunctionContributions functionContributio
functionFactory.jsonReplace_sqlserver();
functionFactory.jsonInsert_sqlserver();
functionFactory.jsonArrayAppend_sqlserver();
functionFactory.jsonArrayInsert_sqlserver();
}
if ( getVersion().isSameOrAfter( 14 ) ) {
functionFactory.listagg_stringAggWithinGroup( "varchar(max)" );
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@
import org.hibernate.dialect.function.json.OracleJsonArrayAggFunction;
import org.hibernate.dialect.function.json.OracleJsonArrayAppendFunction;
import org.hibernate.dialect.function.json.OracleJsonArrayFunction;
import org.hibernate.dialect.function.json.OracleJsonArrayInsertFunction;
import org.hibernate.dialect.function.json.OracleJsonInsertFunction;
import org.hibernate.dialect.function.json.OracleJsonMergepatchFunction;
import org.hibernate.dialect.function.json.OracleJsonObjectAggFunction;
Expand All @@ -128,6 +129,7 @@
import org.hibernate.dialect.function.json.PostgreSQLJsonArrayAggFunction;
import org.hibernate.dialect.function.json.PostgreSQLJsonArrayAppendFunction;
import org.hibernate.dialect.function.json.PostgreSQLJsonArrayFunction;
import org.hibernate.dialect.function.json.PostgreSQLJsonArrayInsertFunction;
import org.hibernate.dialect.function.json.PostgreSQLJsonExistsFunction;
import org.hibernate.dialect.function.json.PostgreSQLJsonInsertFunction;
import org.hibernate.dialect.function.json.PostgreSQLJsonMergepatchFunction;
Expand All @@ -141,6 +143,7 @@
import org.hibernate.dialect.function.json.SQLServerJsonArrayAggFunction;
import org.hibernate.dialect.function.json.SQLServerJsonArrayAppendFunction;
import org.hibernate.dialect.function.json.SQLServerJsonArrayFunction;
import org.hibernate.dialect.function.json.SQLServerJsonArrayInsertFunction;
import org.hibernate.dialect.function.json.SQLServerJsonExistsFunction;
import org.hibernate.dialect.function.json.SQLServerJsonInsertFunction;
import org.hibernate.dialect.function.json.SQLServerJsonObjectAggFunction;
Expand Down Expand Up @@ -4056,4 +4059,42 @@ public void jsonArrayAppend_oracle() {
public void jsonArrayAppend_sqlserver() {
functionRegistry.register( "json_array_append", new SQLServerJsonArrayAppendFunction( typeConfiguration ) );
}

/**
* PostgreSQL json_array_insert() function
*/
public void jsonArrayInsert_postgresql() {
functionRegistry.register( "json_array_insert", new PostgreSQLJsonArrayInsertFunction( typeConfiguration ) );
}

/**
* MySQL json_array_insert() function
*/
public void jsonArrayInsert_mysql() {
functionRegistry.namedDescriptorBuilder( "json_array_insert" )
.setArgumentsValidator( new ArgumentTypesValidator(
StandardArgumentsValidators.exactly( 3 ),
FunctionParameterType.IMPLICIT_JSON,
FunctionParameterType.STRING,
FunctionParameterType.ANY
) )
.setReturnTypeResolver( StandardFunctionReturnTypeResolvers.invariant(
typeConfiguration.getBasicTypeRegistry().resolve( String.class, SqlTypes.JSON )
) )
.register();
}

/**
* Oracle json_array_insert() function
*/
public void jsonArrayInsert_oracle() {
functionRegistry.register( "json_array_insert", new OracleJsonArrayInsertFunction( typeConfiguration ) );
}

/**
* SQL server json_array_insert() function
*/
public void jsonArrayInsert_sqlserver() {
functionRegistry.register( "json_array_insert", new SQLServerJsonArrayInsertFunction( typeConfiguration ) );
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* SPDX-License-Identifier: LGPL-2.1-or-later
* Copyright Red Hat Inc. and Hibernate Authors
*/
package org.hibernate.dialect.function.json;

import org.hibernate.query.sqm.function.AbstractSqmSelfRenderingFunctionDescriptor;
import org.hibernate.query.sqm.function.FunctionKind;
import org.hibernate.query.sqm.produce.function.ArgumentTypesValidator;
import org.hibernate.query.sqm.produce.function.FunctionParameterType;
import org.hibernate.query.sqm.produce.function.StandardArgumentsValidators;
import org.hibernate.query.sqm.produce.function.StandardFunctionReturnTypeResolvers;
import org.hibernate.type.SqlTypes;
import org.hibernate.type.spi.TypeConfiguration;

/**
* Standard json_array_insert function.
*/
public abstract class AbstractJsonArrayInsertFunction extends AbstractSqmSelfRenderingFunctionDescriptor {

public AbstractJsonArrayInsertFunction(TypeConfiguration typeConfiguration) {
super(
"json_array_insert",
FunctionKind.NORMAL,
new ArgumentTypesValidator(
StandardArgumentsValidators.exactly( 3 ),
FunctionParameterType.IMPLICIT_JSON,
FunctionParameterType.STRING,
FunctionParameterType.ANY
),
StandardFunctionReturnTypeResolvers.invariant(
typeConfiguration.getBasicTypeRegistry().resolve( String.class, SqlTypes.JSON )
),
null
);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* SPDX-License-Identifier: LGPL-2.1-or-later
* Copyright Red Hat Inc. and Hibernate Authors
*/
package org.hibernate.dialect.function.json;

import java.util.List;

import org.hibernate.query.ReturnableType;
import org.hibernate.sql.ast.SqlAstTranslator;
import org.hibernate.sql.ast.spi.SqlAppender;
import org.hibernate.sql.ast.tree.SqlAstNode;
import org.hibernate.sql.ast.tree.expression.Expression;
import org.hibernate.type.spi.TypeConfiguration;

/**
* Oracle json_array_insert function.
*/
public class OracleJsonArrayInsertFunction extends AbstractJsonArrayInsertFunction {

public OracleJsonArrayInsertFunction(TypeConfiguration typeConfiguration) {
super( typeConfiguration );
}

@Override
public void render(
SqlAppender sqlAppender,
List<? extends SqlAstNode> arguments,
ReturnableType<?> returnType,
SqlAstTranslator<?> translator) {
final Expression json = (Expression) arguments.get( 0 );
final String jsonPath = translator.getLiteralValue( (Expression) arguments.get( 1 ) );
final SqlAstNode value = arguments.get( 2 );
sqlAppender.appendSql( "json_transform(" );
json.accept( translator );
sqlAppender.appendSql( ",insert " );
sqlAppender.appendSingleQuoteEscapedString( jsonPath );
sqlAppender.appendSql( '=' );
value.accept( translator );
sqlAppender.appendSql( " ignore on existing)" );

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/*
* SPDX-License-Identifier: LGPL-2.1-or-later
* Copyright Red Hat Inc. and Hibernate Authors
*/
package org.hibernate.dialect.function.json;

import java.util.List;

import org.hibernate.QueryException;
import org.hibernate.metamodel.mapping.JdbcMappingContainer;
import org.hibernate.query.ReturnableType;
import org.hibernate.sql.ast.SqlAstTranslator;
import org.hibernate.sql.ast.spi.SqlAppender;
import org.hibernate.sql.ast.tree.SqlAstNode;
import org.hibernate.sql.ast.tree.expression.Expression;
import org.hibernate.sql.ast.tree.expression.Literal;
import org.hibernate.type.spi.TypeConfiguration;

/**
* PostgreSQL json_array_insert function.
*/
public class PostgreSQLJsonArrayInsertFunction extends AbstractJsonArrayInsertFunction {

public PostgreSQLJsonArrayInsertFunction(TypeConfiguration typeConfiguration) {
super( typeConfiguration );
}

@Override
public void render(
SqlAppender sqlAppender,
List<? extends SqlAstNode> arguments,
ReturnableType<?> returnType,
SqlAstTranslator<?> translator) {
final Expression json = (Expression) arguments.get( 0 );
final Expression jsonPath = (Expression) arguments.get( 1 );
final SqlAstNode value = arguments.get( 2 );
sqlAppender.appendSql( "jsonb_insert(" );
final boolean needsCast = !isJsonType( json );
if ( needsCast ) {
sqlAppender.appendSql( "cast(" );
}
json.accept( translator );
if ( needsCast ) {
sqlAppender.appendSql( " as jsonb)" );
}
sqlAppender.appendSql( ',' );
List<JsonPathHelper.JsonPathElement> jsonPathElements =
JsonPathHelper.parseJsonPathElements( translator.getLiteralValue( jsonPath ) );
sqlAppender.appendSql( "array" );
char separator = '[';
for ( JsonPathHelper.JsonPathElement pathElement : jsonPathElements ) {
sqlAppender.appendSql( separator );
if ( pathElement instanceof JsonPathHelper.JsonAttribute attribute ) {
sqlAppender.appendSingleQuoteEscapedString( attribute.attribute() );
}
else if ( pathElement instanceof JsonPathHelper.JsonParameterIndexAccess ) {
final String parameterName = ( (JsonPathHelper.JsonParameterIndexAccess) pathElement ).parameterName();
throw new QueryException( "JSON path [" + jsonPath + "] uses parameter [" + parameterName + "] that is not passed" );
}
else {
sqlAppender.appendSql( '\'' );
sqlAppender.appendSql( ( (JsonPathHelper.JsonIndexAccess) pathElement ).index() );
sqlAppender.appendSql( '\'' );
}
separator = ',';
}
sqlAppender.appendSql( "]::text[]," );
if ( value instanceof Literal && ( (Literal) value ).getLiteralValue() == null ) {
sqlAppender.appendSql( "null::jsonb" );
}
else {
sqlAppender.appendSql( "to_jsonb(" );
value.accept( translator );
if ( value instanceof Literal literal && literal.getJdbcMapping().getJdbcType().isString() ) {
// PostgreSQL until version 16 is not smart enough to infer the type of a string literal
sqlAppender.appendSql( "::text" );
}
sqlAppender.appendSql( ')' );
}
sqlAppender.appendSql( ')' );
}

private static boolean isJsonType(Expression expression) {
final JdbcMappingContainer expressionType = expression.getExpressionType();
return expressionType != null && expressionType.getSingleJdbcMapping().getJdbcType().isJson();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public void render(
final Expression json = (Expression) arguments.get( 0 );
final Expression jsonPath = (Expression) arguments.get( 1 );
final SqlAstNode value = arguments.get( 2 );
sqlAppender.appendSql( "(select case when t.d#>>t.p is not null then t.d else jsonb_insert(t.d,t.p," );
sqlAppender.appendSql( "(select case when (t.d)#>>t.p is not null then t.d else jsonb_insert(t.d,t.p," );
if ( value instanceof Literal && ( (Literal) value ).getLiteralValue() == null ) {
sqlAppender.appendSql( "null::jsonb" );
}
Expand Down
Loading

0 comments on commit 0e5846b

Please sign in to comment.