HHH-18604 Add json_array_insert
This commit is contained in:
parent
8dfc2a5a91
commit
0e5846b805
|
@ -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_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_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_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
|
||||||
|===
|
|===
|
||||||
|
|
||||||
|
|
||||||
|
@ -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.
|
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]]
|
[[hql-user-defined-functions]]
|
||||||
==== Native and user-defined functions
|
==== Native and user-defined functions
|
||||||
|
|
||||||
|
|
|
@ -514,6 +514,7 @@ public class CockroachLegacyDialect extends Dialect {
|
||||||
functionFactory.jsonInsert_postgresql();
|
functionFactory.jsonInsert_postgresql();
|
||||||
functionFactory.jsonMergepatch_postgresql();
|
functionFactory.jsonMergepatch_postgresql();
|
||||||
functionFactory.jsonArrayAppend_postgresql();
|
functionFactory.jsonArrayAppend_postgresql();
|
||||||
|
functionFactory.jsonArrayInsert_postgresql();
|
||||||
|
|
||||||
// Postgres uses # instead of ^ for XOR
|
// Postgres uses # instead of ^ for XOR
|
||||||
functionContributions.getFunctionRegistry().patternDescriptorBuilder( "bitxor", "(?1#?2)" )
|
functionContributions.getFunctionRegistry().patternDescriptorBuilder( "bitxor", "(?1#?2)" )
|
||||||
|
|
|
@ -666,6 +666,7 @@ public class MySQLLegacyDialect extends Dialect {
|
||||||
functionFactory.jsonInsert_mysql();
|
functionFactory.jsonInsert_mysql();
|
||||||
functionFactory.jsonMergepatch_mysql();
|
functionFactory.jsonMergepatch_mysql();
|
||||||
functionFactory.jsonArrayAppend_mysql();
|
functionFactory.jsonArrayAppend_mysql();
|
||||||
|
functionFactory.jsonArrayInsert_mysql();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -323,6 +323,7 @@ public class OracleLegacyDialect extends Dialect {
|
||||||
functionFactory.jsonInsert_oracle();
|
functionFactory.jsonInsert_oracle();
|
||||||
functionFactory.jsonMergepatch_oracle();
|
functionFactory.jsonMergepatch_oracle();
|
||||||
functionFactory.jsonArrayAppend_oracle();
|
functionFactory.jsonArrayAppend_oracle();
|
||||||
|
functionFactory.jsonArrayInsert_oracle();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -664,6 +664,7 @@ public class PostgreSQLLegacyDialect extends Dialect {
|
||||||
functionFactory.jsonInsert_postgresql();
|
functionFactory.jsonInsert_postgresql();
|
||||||
functionFactory.jsonMergepatch_postgresql();
|
functionFactory.jsonMergepatch_postgresql();
|
||||||
functionFactory.jsonArrayAppend_postgresql();
|
functionFactory.jsonArrayAppend_postgresql();
|
||||||
|
functionFactory.jsonArrayInsert_postgresql();
|
||||||
|
|
||||||
if ( getVersion().isSameOrAfter( 9, 4 ) ) {
|
if ( getVersion().isSameOrAfter( 9, 4 ) ) {
|
||||||
functionFactory.makeDateTimeTimestamp();
|
functionFactory.makeDateTimeTimestamp();
|
||||||
|
|
|
@ -411,6 +411,7 @@ public class SQLServerLegacyDialect extends AbstractTransactSQLDialect {
|
||||||
functionFactory.jsonReplace_sqlserver();
|
functionFactory.jsonReplace_sqlserver();
|
||||||
functionFactory.jsonInsert_sqlserver();
|
functionFactory.jsonInsert_sqlserver();
|
||||||
functionFactory.jsonArrayAppend_sqlserver();
|
functionFactory.jsonArrayAppend_sqlserver();
|
||||||
|
functionFactory.jsonArrayInsert_sqlserver();
|
||||||
}
|
}
|
||||||
if ( getVersion().isSameOrAfter( 14 ) ) {
|
if ( getVersion().isSameOrAfter( 14 ) ) {
|
||||||
functionFactory.listagg_stringAggWithinGroup( "varchar(max)" );
|
functionFactory.listagg_stringAggWithinGroup( "varchar(max)" );
|
||||||
|
|
|
@ -481,6 +481,7 @@ public class CockroachDialect extends Dialect {
|
||||||
functionFactory.jsonInsert_postgresql();
|
functionFactory.jsonInsert_postgresql();
|
||||||
functionFactory.jsonMergepatch_postgresql();
|
functionFactory.jsonMergepatch_postgresql();
|
||||||
functionFactory.jsonArrayAppend_postgresql();
|
functionFactory.jsonArrayAppend_postgresql();
|
||||||
|
functionFactory.jsonArrayInsert_postgresql();
|
||||||
|
|
||||||
// Postgres uses # instead of ^ for XOR
|
// Postgres uses # instead of ^ for XOR
|
||||||
functionContributions.getFunctionRegistry().patternDescriptorBuilder( "bitxor", "(?1#?2)" )
|
functionContributions.getFunctionRegistry().patternDescriptorBuilder( "bitxor", "(?1#?2)" )
|
||||||
|
|
|
@ -651,6 +651,7 @@ public class MySQLDialect extends Dialect {
|
||||||
functionFactory.jsonInsert_mysql();
|
functionFactory.jsonInsert_mysql();
|
||||||
functionFactory.jsonMergepatch_mysql();
|
functionFactory.jsonMergepatch_mysql();
|
||||||
functionFactory.jsonArrayAppend_mysql();
|
functionFactory.jsonArrayAppend_mysql();
|
||||||
|
functionFactory.jsonArrayInsert_mysql();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -414,6 +414,7 @@ public class OracleDialect extends Dialect {
|
||||||
functionFactory.jsonInsert_oracle();
|
functionFactory.jsonInsert_oracle();
|
||||||
functionFactory.jsonMergepatch_oracle();
|
functionFactory.jsonMergepatch_oracle();
|
||||||
functionFactory.jsonArrayAppend_oracle();
|
functionFactory.jsonArrayAppend_oracle();
|
||||||
|
functionFactory.jsonArrayInsert_oracle();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -625,6 +625,7 @@ public class PostgreSQLDialect extends Dialect {
|
||||||
functionFactory.jsonInsert_postgresql();
|
functionFactory.jsonInsert_postgresql();
|
||||||
functionFactory.jsonMergepatch_postgresql();
|
functionFactory.jsonMergepatch_postgresql();
|
||||||
functionFactory.jsonArrayAppend_postgresql();
|
functionFactory.jsonArrayAppend_postgresql();
|
||||||
|
functionFactory.jsonArrayInsert_postgresql();
|
||||||
|
|
||||||
functionFactory.makeDateTimeTimestamp();
|
functionFactory.makeDateTimeTimestamp();
|
||||||
// Note that PostgreSQL doesn't support the OVER clause for ordered set-aggregate functions
|
// Note that PostgreSQL doesn't support the OVER clause for ordered set-aggregate functions
|
||||||
|
|
|
@ -429,6 +429,7 @@ public class SQLServerDialect extends AbstractTransactSQLDialect {
|
||||||
functionFactory.jsonReplace_sqlserver();
|
functionFactory.jsonReplace_sqlserver();
|
||||||
functionFactory.jsonInsert_sqlserver();
|
functionFactory.jsonInsert_sqlserver();
|
||||||
functionFactory.jsonArrayAppend_sqlserver();
|
functionFactory.jsonArrayAppend_sqlserver();
|
||||||
|
functionFactory.jsonArrayInsert_sqlserver();
|
||||||
}
|
}
|
||||||
if ( getVersion().isSameOrAfter( 14 ) ) {
|
if ( getVersion().isSameOrAfter( 14 ) ) {
|
||||||
functionFactory.listagg_stringAggWithinGroup( "varchar(max)" );
|
functionFactory.listagg_stringAggWithinGroup( "varchar(max)" );
|
||||||
|
|
|
@ -118,6 +118,7 @@ import org.hibernate.dialect.function.json.MySQLJsonValueFunction;
|
||||||
import org.hibernate.dialect.function.json.OracleJsonArrayAggFunction;
|
import org.hibernate.dialect.function.json.OracleJsonArrayAggFunction;
|
||||||
import org.hibernate.dialect.function.json.OracleJsonArrayAppendFunction;
|
import org.hibernate.dialect.function.json.OracleJsonArrayAppendFunction;
|
||||||
import org.hibernate.dialect.function.json.OracleJsonArrayFunction;
|
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.OracleJsonInsertFunction;
|
||||||
import org.hibernate.dialect.function.json.OracleJsonMergepatchFunction;
|
import org.hibernate.dialect.function.json.OracleJsonMergepatchFunction;
|
||||||
import org.hibernate.dialect.function.json.OracleJsonObjectAggFunction;
|
import org.hibernate.dialect.function.json.OracleJsonObjectAggFunction;
|
||||||
|
@ -128,6 +129,7 @@ import org.hibernate.dialect.function.json.OracleJsonSetFunction;
|
||||||
import org.hibernate.dialect.function.json.PostgreSQLJsonArrayAggFunction;
|
import org.hibernate.dialect.function.json.PostgreSQLJsonArrayAggFunction;
|
||||||
import org.hibernate.dialect.function.json.PostgreSQLJsonArrayAppendFunction;
|
import org.hibernate.dialect.function.json.PostgreSQLJsonArrayAppendFunction;
|
||||||
import org.hibernate.dialect.function.json.PostgreSQLJsonArrayFunction;
|
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.PostgreSQLJsonExistsFunction;
|
||||||
import org.hibernate.dialect.function.json.PostgreSQLJsonInsertFunction;
|
import org.hibernate.dialect.function.json.PostgreSQLJsonInsertFunction;
|
||||||
import org.hibernate.dialect.function.json.PostgreSQLJsonMergepatchFunction;
|
import org.hibernate.dialect.function.json.PostgreSQLJsonMergepatchFunction;
|
||||||
|
@ -141,6 +143,7 @@ import org.hibernate.dialect.function.json.PostgreSQLJsonValueFunction;
|
||||||
import org.hibernate.dialect.function.json.SQLServerJsonArrayAggFunction;
|
import org.hibernate.dialect.function.json.SQLServerJsonArrayAggFunction;
|
||||||
import org.hibernate.dialect.function.json.SQLServerJsonArrayAppendFunction;
|
import org.hibernate.dialect.function.json.SQLServerJsonArrayAppendFunction;
|
||||||
import org.hibernate.dialect.function.json.SQLServerJsonArrayFunction;
|
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.SQLServerJsonExistsFunction;
|
||||||
import org.hibernate.dialect.function.json.SQLServerJsonInsertFunction;
|
import org.hibernate.dialect.function.json.SQLServerJsonInsertFunction;
|
||||||
import org.hibernate.dialect.function.json.SQLServerJsonObjectAggFunction;
|
import org.hibernate.dialect.function.json.SQLServerJsonObjectAggFunction;
|
||||||
|
@ -4056,4 +4059,42 @@ public class CommonFunctionFactory {
|
||||||
public void jsonArrayAppend_sqlserver() {
|
public void jsonArrayAppend_sqlserver() {
|
||||||
functionRegistry.register( "json_array_append", new SQLServerJsonArrayAppendFunction( typeConfiguration ) );
|
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 ) );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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)" );
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -34,7 +34,7 @@ public class PostgreSQLJsonInsertFunction extends AbstractJsonInsertFunction {
|
||||||
final Expression json = (Expression) arguments.get( 0 );
|
final Expression json = (Expression) arguments.get( 0 );
|
||||||
final Expression jsonPath = (Expression) arguments.get( 1 );
|
final Expression jsonPath = (Expression) arguments.get( 1 );
|
||||||
final SqlAstNode value = arguments.get( 2 );
|
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 ) {
|
if ( value instanceof Literal && ( (Literal) value ).getLiteralValue() == null ) {
|
||||||
sqlAppender.appendSql( "null::jsonb" );
|
sqlAppender.appendSql( "null::jsonb" );
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,88 @@
|
||||||
|
/*
|
||||||
|
* 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.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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SQL Server json_array_insert function.
|
||||||
|
*/
|
||||||
|
public class SQLServerJsonArrayInsertFunction extends AbstractJsonArrayInsertFunction {
|
||||||
|
|
||||||
|
public SQLServerJsonArrayInsertFunction(TypeConfiguration typeConfiguration) {
|
||||||
|
super( typeConfiguration );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void render(
|
||||||
|
SqlAppender sqlAppender,
|
||||||
|
List<? extends SqlAstNode> arguments,
|
||||||
|
ReturnableType<?> returnType,
|
||||||
|
SqlAstTranslator<?> translator) {
|
||||||
|
final String jsonPath = translator.<String>getLiteralValue( (Expression) arguments.get( 1 ) ).trim();
|
||||||
|
final int bracketEndIndex = jsonPath.lastIndexOf( ']' );
|
||||||
|
final int bracketStartIndex = jsonPath.lastIndexOf( '[' );
|
||||||
|
if ( jsonPath.isEmpty()
|
||||||
|
|| bracketEndIndex != jsonPath.length() - 1
|
||||||
|
|| bracketStartIndex == -1 ) {
|
||||||
|
throw new QueryException( "JSON path does not end with an array index: " + jsonPath );
|
||||||
|
}
|
||||||
|
final int index;
|
||||||
|
try {
|
||||||
|
index = Integer.parseInt( jsonPath.substring( bracketStartIndex + 1, bracketEndIndex ) );
|
||||||
|
}
|
||||||
|
catch ( NumberFormatException e ) {
|
||||||
|
throw new QueryException( "JSON path does not point to a valid array index: " + jsonPath );
|
||||||
|
}
|
||||||
|
final Expression json = (Expression) arguments.get( 0 );
|
||||||
|
final SqlAstNode value = arguments.get( 2 );
|
||||||
|
// Only replace data if this is an array
|
||||||
|
sqlAppender.appendSql( "(select case when left(json_query(x.d,x.p),1)='[' then " );
|
||||||
|
// Replace the array
|
||||||
|
sqlAppender.appendSql( "json_modify(x.d,x.p,json_query((" );
|
||||||
|
// Aggregate a new JSON array based on element rows
|
||||||
|
sqlAppender.appendSql( "select '['+string_agg(t.v,',') within group (order by t.k)+']' from (" );
|
||||||
|
|
||||||
|
sqlAppender.appendSql( "select x.i k,x.v v union all " );
|
||||||
|
sqlAppender.appendSql( "select case when cast(t.[key] as int)>=x.i then cast(t.[key] as int)+1 " );
|
||||||
|
sqlAppender.appendSql( "else cast(t.[key] as int) end," );
|
||||||
|
// type 0 is a null literal
|
||||||
|
sqlAppender.appendSql( "case t.type when 0 then 'null' when 1 then ");
|
||||||
|
// type 1 is a string literal. to quote it, we use for json path and trim the string down to just the value
|
||||||
|
sqlAppender.appendSql(
|
||||||
|
"(select substring(a.v,6,len(a.v)-6) from (select t.value a for json path,without_array_wrapper) a(v))" );
|
||||||
|
sqlAppender.appendSql( " else t.value end from openjson(x.d,x.p) t) t))) " );
|
||||||
|
sqlAppender.appendSql( " else x.d end " );
|
||||||
|
// Push args into a values clause since we are going to refer to them multiple times
|
||||||
|
sqlAppender.appendSql( "from (values(" );
|
||||||
|
json.accept( translator );
|
||||||
|
sqlAppender.append( ',' );
|
||||||
|
sqlAppender.appendSingleQuoteEscapedString( jsonPath.substring( 0, bracketStartIndex ) );
|
||||||
|
sqlAppender.append( ',' );
|
||||||
|
sqlAppender.appendSql( index );
|
||||||
|
sqlAppender.append( ',' );
|
||||||
|
value.accept( translator );
|
||||||
|
sqlAppender.append( ")) x(d,p,i,v))" );
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void renderArgument(
|
||||||
|
SqlAppender sqlAppender,
|
||||||
|
SqlAstNode arg,
|
||||||
|
SqlAstTranslator<?> translator) {
|
||||||
|
sqlAppender.appendSql( "substring(json_array(" );
|
||||||
|
arg.accept( translator );
|
||||||
|
sqlAppender.appendSql( " null on null),2,len(json_array(" );
|
||||||
|
arg.accept( translator );
|
||||||
|
sqlAppender.appendSql( " null on null))-2)" );
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
/*
|
||||||
|
* SPDX-License-Identifier: LGPL-2.1-or-later
|
||||||
|
* Copyright Red Hat Inc. and Hibernate Authors
|
||||||
|
*/
|
||||||
|
package org.hibernate.orm.test.function.json;
|
||||||
|
|
||||||
|
import org.hibernate.cfg.QuerySettings;
|
||||||
|
|
||||||
|
import org.hibernate.testing.orm.domain.StandardDomainModel;
|
||||||
|
import org.hibernate.testing.orm.junit.DialectFeatureChecks;
|
||||||
|
import org.hibernate.testing.orm.junit.DomainModel;
|
||||||
|
import org.hibernate.testing.orm.junit.RequiresDialectFeature;
|
||||||
|
import org.hibernate.testing.orm.junit.ServiceRegistry;
|
||||||
|
import org.hibernate.testing.orm.junit.SessionFactory;
|
||||||
|
import org.hibernate.testing.orm.junit.SessionFactoryScope;
|
||||||
|
import org.hibernate.testing.orm.junit.Setting;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Christian Beikov
|
||||||
|
*/
|
||||||
|
@DomainModel(standardModels = StandardDomainModel.GAMBIT)
|
||||||
|
@SessionFactory
|
||||||
|
@ServiceRegistry(settings = @Setting(name = QuerySettings.JSON_FUNCTIONS_ENABLED, value = "true"))
|
||||||
|
@RequiresDialectFeature( feature = DialectFeatureChecks.SupportsJsonInsert.class)
|
||||||
|
public class JsonArrayInsertTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSimple(SessionFactoryScope scope) {
|
||||||
|
scope.inSession( em -> {
|
||||||
|
//tag::hql-json-array-insert-example[]
|
||||||
|
em.createQuery( "select json_array_insert('{\"a\":[1]}', '$.a[0]', 2)" ).getResultList();
|
||||||
|
//end::hql-json-array-insert-example[]
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -704,6 +704,70 @@ public class JsonFunctionTests {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsJsonArrayInsert.class)
|
||||||
|
public void testJsonArrayInsert(SessionFactoryScope scope) {
|
||||||
|
scope.inTransaction(
|
||||||
|
session -> {
|
||||||
|
String json = session.createQuery(
|
||||||
|
"select json_array_insert('{\"b\":[2]}', '$.b[0]', 1)",
|
||||||
|
String.class
|
||||||
|
).getSingleResult();
|
||||||
|
Map<String, Object> object = parseObject( json );
|
||||||
|
assertEquals( 1, object.size() );
|
||||||
|
assertEquals( Arrays.asList( 1, 2 ), object.get( "b" ) );
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsJsonArrayInsert.class)
|
||||||
|
public void testJsonArrayInsertNonExisting(SessionFactoryScope scope) {
|
||||||
|
scope.inTransaction(
|
||||||
|
session -> {
|
||||||
|
String json = session.createQuery(
|
||||||
|
"select json_array_insert('{\"b\":[2]}', '$.c[0]', 1)",
|
||||||
|
String.class
|
||||||
|
).getSingleResult();
|
||||||
|
Map<String, Object> object = parseObject( json );
|
||||||
|
assertEquals( 1, object.size() );
|
||||||
|
assertEquals( List.of( 2 ), object.get( "b" ) );
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsJsonArrayInsert.class)
|
||||||
|
public void testJsonArrayInsertNonArray(SessionFactoryScope scope) {
|
||||||
|
scope.inTransaction(
|
||||||
|
session -> {
|
||||||
|
String json = session.createQuery(
|
||||||
|
"select json_array_insert('{\"b\":2}', '$.b[0]', 1)",
|
||||||
|
String.class
|
||||||
|
).getSingleResult();
|
||||||
|
Map<String, Object> object = parseObject( json );
|
||||||
|
assertEquals( 1, object.size() );
|
||||||
|
assertEquals( 2, object.get( "b" ) );
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsJsonArrayInsert.class)
|
||||||
|
public void testJsonArrayInsertToNull(SessionFactoryScope scope) {
|
||||||
|
scope.inTransaction(
|
||||||
|
session -> {
|
||||||
|
String json = session.createQuery(
|
||||||
|
"select json_array_insert('{\"b\":null}', '$.b[0]', 1)",
|
||||||
|
String.class
|
||||||
|
).getSingleResult();
|
||||||
|
Map<String, Object> object = parseObject( json );
|
||||||
|
assertEquals( 1, object.size() );
|
||||||
|
assertNull( object.get( "b" ) );
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
private static final ObjectMapper MAPPER = new ObjectMapper();
|
private static final ObjectMapper MAPPER = new ObjectMapper();
|
||||||
|
|
||||||
private static Map<String, Object> parseObject(String json) {
|
private static Map<String, Object> parseObject(String json) {
|
||||||
|
|
|
@ -829,6 +829,12 @@ abstract public class DialectFeatureChecks {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class SupportsJsonArrayInsert implements DialectFeatureCheck {
|
||||||
|
public boolean apply(Dialect dialect) {
|
||||||
|
return definesFunction( dialect, "json_array_insert" );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static class IsJtds implements DialectFeatureCheck {
|
public static class IsJtds implements DialectFeatureCheck {
|
||||||
public boolean apply(Dialect dialect) {
|
public boolean apply(Dialect dialect) {
|
||||||
return dialect instanceof SybaseDialect && ( (SybaseDialect) dialect ).getDriverKind() == SybaseDriverKind.JTDS;
|
return dialect instanceof SybaseDialect && ( (SybaseDialect) dialect ).getDriverKind() == SybaseDriverKind.JTDS;
|
||||||
|
|
Loading…
Reference in New Issue