HHH-18604 Add json_array_append function
This commit is contained in:
parent
4a6c555cd0
commit
8dfc2a5a91
|
@ -1642,6 +1642,7 @@ it is necessary to enable the `hibernate.query.hql.json_functions_enabled` confi
|
|||
| `json_set()` | Inserts/Replaces 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_array_append()` | Appends to a JSON array of a JSON document by JSON path
|
||||
|===
|
||||
|
||||
|
||||
|
@ -2113,6 +2114,26 @@ include::{json-example-dir-hql}/JsonMergepatchTest.java[tags=hql-json-mergepatch
|
|||
|
||||
WARNING: SAP HANA, DB2, SQL Server, H2 and HSQLDB do not support this function. On PostgreSQL, this function is emulated.
|
||||
|
||||
[[hql-json-array-append-function]]
|
||||
===== `json_array_append()`
|
||||
|
||||
Appends 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.
|
||||
|
||||
If the value within the JSON document as identified by the JSON path is not a JSON array,
|
||||
it is auto-wrapped into an array.
|
||||
When no value exists for a JSON path, the document is not changed.
|
||||
|
||||
[[hql-json-array-append-example]]
|
||||
====
|
||||
[source, java, indent=0]
|
||||
----
|
||||
include::{json-example-dir-hql}/JsonArrayAppendTest.java[tags=hql-json-array-append-example]
|
||||
----
|
||||
====
|
||||
|
||||
WARNING: SAP HANA, DB2, H2 and HSQLDB do not support this function.
|
||||
|
||||
[[hql-user-defined-functions]]
|
||||
==== Native and user-defined functions
|
||||
|
||||
|
|
|
@ -513,6 +513,7 @@ public class CockroachLegacyDialect extends Dialect {
|
|||
functionFactory.jsonReplace_postgresql();
|
||||
functionFactory.jsonInsert_postgresql();
|
||||
functionFactory.jsonMergepatch_postgresql();
|
||||
functionFactory.jsonArrayAppend_postgresql();
|
||||
|
||||
// Postgres uses # instead of ^ for XOR
|
||||
functionContributions.getFunctionRegistry().patternDescriptorBuilder( "bitxor", "(?1#?2)" )
|
||||
|
|
|
@ -95,6 +95,7 @@ public class MariaDBLegacyDialect extends MySQLLegacyDialect {
|
|||
commonFunctionFactory.jsonQuery_mariadb();
|
||||
commonFunctionFactory.jsonArrayAgg_mariadb();
|
||||
commonFunctionFactory.jsonObjectAgg_mariadb();
|
||||
commonFunctionFactory.jsonArrayAppend_mariadb();
|
||||
if ( getVersion().isSameOrAfter( 10, 3, 3 ) ) {
|
||||
commonFunctionFactory.inverseDistributionOrderedSetAggregates_windowEmulation();
|
||||
functionContributions.getFunctionRegistry().patternDescriptorBuilder( "median", "median(?1) over ()" )
|
||||
|
|
|
@ -665,6 +665,7 @@ public class MySQLLegacyDialect extends Dialect {
|
|||
functionFactory.jsonReplace_mysql();
|
||||
functionFactory.jsonInsert_mysql();
|
||||
functionFactory.jsonMergepatch_mysql();
|
||||
functionFactory.jsonArrayAppend_mysql();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -322,6 +322,7 @@ public class OracleLegacyDialect extends Dialect {
|
|||
functionFactory.jsonReplace_oracle();
|
||||
functionFactory.jsonInsert_oracle();
|
||||
functionFactory.jsonMergepatch_oracle();
|
||||
functionFactory.jsonArrayAppend_oracle();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -663,6 +663,7 @@ public class PostgreSQLLegacyDialect extends Dialect {
|
|||
functionFactory.jsonReplace_postgresql();
|
||||
functionFactory.jsonInsert_postgresql();
|
||||
functionFactory.jsonMergepatch_postgresql();
|
||||
functionFactory.jsonArrayAppend_postgresql();
|
||||
|
||||
if ( getVersion().isSameOrAfter( 9, 4 ) ) {
|
||||
functionFactory.makeDateTimeTimestamp();
|
||||
|
|
|
@ -410,6 +410,7 @@ public class SQLServerLegacyDialect extends AbstractTransactSQLDialect {
|
|||
functionFactory.jsonRemove_sqlserver();
|
||||
functionFactory.jsonReplace_sqlserver();
|
||||
functionFactory.jsonInsert_sqlserver();
|
||||
functionFactory.jsonArrayAppend_sqlserver();
|
||||
}
|
||||
if ( getVersion().isSameOrAfter( 14 ) ) {
|
||||
functionFactory.listagg_stringAggWithinGroup( "varchar(max)" );
|
||||
|
|
|
@ -480,6 +480,7 @@ public class CockroachDialect extends Dialect {
|
|||
functionFactory.jsonReplace_postgresql();
|
||||
functionFactory.jsonInsert_postgresql();
|
||||
functionFactory.jsonMergepatch_postgresql();
|
||||
functionFactory.jsonArrayAppend_postgresql();
|
||||
|
||||
// Postgres uses # instead of ^ for XOR
|
||||
functionContributions.getFunctionRegistry().patternDescriptorBuilder( "bitxor", "(?1#?2)" )
|
||||
|
|
|
@ -98,6 +98,7 @@ public class MariaDBDialect extends MySQLDialect {
|
|||
commonFunctionFactory.jsonQuery_mariadb();
|
||||
commonFunctionFactory.jsonArrayAgg_mariadb();
|
||||
commonFunctionFactory.jsonObjectAgg_mariadb();
|
||||
commonFunctionFactory.jsonArrayAppend_mariadb();
|
||||
commonFunctionFactory.inverseDistributionOrderedSetAggregates_windowEmulation();
|
||||
functionContributions.getFunctionRegistry().patternDescriptorBuilder( "median", "median(?1) over ()" )
|
||||
.setInvariantType( functionContributions.getTypeConfiguration().getBasicTypeRegistry().resolve( StandardBasicTypes.DOUBLE ) )
|
||||
|
|
|
@ -650,6 +650,7 @@ public class MySQLDialect extends Dialect {
|
|||
functionFactory.jsonReplace_mysql();
|
||||
functionFactory.jsonInsert_mysql();
|
||||
functionFactory.jsonMergepatch_mysql();
|
||||
functionFactory.jsonArrayAppend_mysql();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -413,6 +413,7 @@ public class OracleDialect extends Dialect {
|
|||
functionFactory.jsonReplace_oracle();
|
||||
functionFactory.jsonInsert_oracle();
|
||||
functionFactory.jsonMergepatch_oracle();
|
||||
functionFactory.jsonArrayAppend_oracle();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -624,6 +624,7 @@ public class PostgreSQLDialect extends Dialect {
|
|||
functionFactory.jsonReplace_postgresql();
|
||||
functionFactory.jsonInsert_postgresql();
|
||||
functionFactory.jsonMergepatch_postgresql();
|
||||
functionFactory.jsonArrayAppend_postgresql();
|
||||
|
||||
functionFactory.makeDateTimeTimestamp();
|
||||
// Note that PostgreSQL doesn't support the OVER clause for ordered set-aggregate functions
|
||||
|
|
|
@ -428,6 +428,7 @@ public class SQLServerDialect extends AbstractTransactSQLDialect {
|
|||
functionFactory.jsonRemove_sqlserver();
|
||||
functionFactory.jsonReplace_sqlserver();
|
||||
functionFactory.jsonInsert_sqlserver();
|
||||
functionFactory.jsonArrayAppend_sqlserver();
|
||||
}
|
||||
if ( getVersion().isSameOrAfter( 14 ) ) {
|
||||
functionFactory.listagg_stringAggWithinGroup( "varchar(max)" );
|
||||
|
|
|
@ -103,6 +103,7 @@ import org.hibernate.dialect.function.json.JsonObjectFunction;
|
|||
import org.hibernate.dialect.function.json.JsonQueryFunction;
|
||||
import org.hibernate.dialect.function.json.JsonValueFunction;
|
||||
import org.hibernate.dialect.function.json.MariaDBJsonArrayAggFunction;
|
||||
import org.hibernate.dialect.function.json.MariaDBJsonArrayAppendFunction;
|
||||
import org.hibernate.dialect.function.json.MariaDBJsonArrayFunction;
|
||||
import org.hibernate.dialect.function.json.MariaDBJsonObjectAggFunction;
|
||||
import org.hibernate.dialect.function.json.MariaDBJsonQueryFunction;
|
||||
|
@ -115,6 +116,7 @@ import org.hibernate.dialect.function.json.MySQLJsonObjectFunction;
|
|||
import org.hibernate.dialect.function.json.MySQLJsonQueryFunction;
|
||||
import org.hibernate.dialect.function.json.MySQLJsonValueFunction;
|
||||
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.OracleJsonInsertFunction;
|
||||
import org.hibernate.dialect.function.json.OracleJsonMergepatchFunction;
|
||||
|
@ -124,6 +126,7 @@ import org.hibernate.dialect.function.json.OracleJsonRemoveFunction;
|
|||
import org.hibernate.dialect.function.json.OracleJsonReplaceFunction;
|
||||
import org.hibernate.dialect.function.json.OracleJsonSetFunction;
|
||||
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.PostgreSQLJsonExistsFunction;
|
||||
import org.hibernate.dialect.function.json.PostgreSQLJsonInsertFunction;
|
||||
|
@ -136,6 +139,7 @@ import org.hibernate.dialect.function.json.PostgreSQLJsonReplaceFunction;
|
|||
import org.hibernate.dialect.function.json.PostgreSQLJsonSetFunction;
|
||||
import org.hibernate.dialect.function.json.PostgreSQLJsonValueFunction;
|
||||
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.SQLServerJsonExistsFunction;
|
||||
import org.hibernate.dialect.function.json.SQLServerJsonInsertFunction;
|
||||
|
@ -4007,4 +4011,49 @@ public class CommonFunctionFactory {
|
|||
public void jsonMergepatch_oracle() {
|
||||
functionRegistry.register( "json_mergepatch", new OracleJsonMergepatchFunction( typeConfiguration ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* PostgreSQL json_array_append() function
|
||||
*/
|
||||
public void jsonArrayAppend_postgresql() {
|
||||
functionRegistry.register( "json_array_append", new PostgreSQLJsonArrayAppendFunction( typeConfiguration ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* MySQL json_array_append() function
|
||||
*/
|
||||
public void jsonArrayAppend_mysql() {
|
||||
functionRegistry.namedDescriptorBuilder( "json_array_append" )
|
||||
.setArgumentsValidator( new ArgumentTypesValidator(
|
||||
StandardArgumentsValidators.exactly( 3 ),
|
||||
FunctionParameterType.IMPLICIT_JSON,
|
||||
FunctionParameterType.STRING,
|
||||
FunctionParameterType.ANY
|
||||
) )
|
||||
.setReturnTypeResolver( StandardFunctionReturnTypeResolvers.invariant(
|
||||
typeConfiguration.getBasicTypeRegistry().resolve( String.class, SqlTypes.JSON )
|
||||
) )
|
||||
.register();
|
||||
}
|
||||
|
||||
/**
|
||||
* MariaDB json_array_append() function
|
||||
*/
|
||||
public void jsonArrayAppend_mariadb() {
|
||||
functionRegistry.register( "json_array_append", new MariaDBJsonArrayAppendFunction( typeConfiguration ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Oracle json_array_append() function
|
||||
*/
|
||||
public void jsonArrayAppend_oracle() {
|
||||
functionRegistry.register( "json_array_append", new OracleJsonArrayAppendFunction( typeConfiguration ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* SQL server json_array_append() function
|
||||
*/
|
||||
public void jsonArrayAppend_sqlserver() {
|
||||
functionRegistry.register( "json_array_append", new SQLServerJsonArrayAppendFunction( 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_append function.
|
||||
*/
|
||||
public abstract class AbstractJsonArrayAppendFunction extends AbstractSqmSelfRenderingFunctionDescriptor {
|
||||
|
||||
public AbstractJsonArrayAppendFunction(TypeConfiguration typeConfiguration) {
|
||||
super(
|
||||
"json_array_append",
|
||||
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,47 @@
|
|||
/*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* MariaDB json_array_append function.
|
||||
*/
|
||||
public class MariaDBJsonArrayAppendFunction extends AbstractJsonArrayAppendFunction {
|
||||
|
||||
public MariaDBJsonArrayAppendFunction(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( "json_replace(" );
|
||||
json.accept( translator );
|
||||
sqlAppender.appendSql( ',' );
|
||||
jsonPath.accept( translator );
|
||||
sqlAppender.appendSql( ",json_merge(json_extract(" );
|
||||
json.accept( translator );
|
||||
sqlAppender.appendSql( ',' );
|
||||
jsonPath.accept( translator );
|
||||
sqlAppender.appendSql( "),json_array(" );
|
||||
value.accept( translator );
|
||||
sqlAppender.appendSql( ")))" );
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
* 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_append function.
|
||||
*/
|
||||
public class OracleJsonArrayAppendFunction extends AbstractJsonArrayAppendFunction {
|
||||
|
||||
public OracleJsonArrayAppendFunction(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( "(select case coalesce(json_value(t.d,'" );
|
||||
for ( int i = 0; i < jsonPath.length(); i++ ) {
|
||||
final char c = jsonPath.charAt( i );
|
||||
if ( c == '\'') {
|
||||
sqlAppender.appendSql( "'" );
|
||||
}
|
||||
sqlAppender.appendSql( c );
|
||||
}
|
||||
sqlAppender.appendSql( ".type()'),'x') when 'x' then t.d when 'array' then json_transform(t.d,append " );
|
||||
sqlAppender.appendSingleQuoteEscapedString( jsonPath );
|
||||
sqlAppender.appendSql( "=t.v) when 'object' then json_transform(t.d,set " );
|
||||
sqlAppender.appendSingleQuoteEscapedString( jsonPath );
|
||||
sqlAppender.appendSql( "=json_array(coalesce(json_query(t.d," );
|
||||
sqlAppender.appendSingleQuoteEscapedString( jsonPath );
|
||||
sqlAppender.appendSql( "),'null') format json,t.v)) else json_transform(t.d,set " );
|
||||
sqlAppender.appendSingleQuoteEscapedString( jsonPath );
|
||||
sqlAppender.appendSql( "=json_array(coalesce(json_value(t.d," );
|
||||
sqlAppender.appendSingleQuoteEscapedString( jsonPath );
|
||||
sqlAppender.appendSql( "),'null') format json,t.v)) end from (select " );
|
||||
json.accept( translator );
|
||||
sqlAppender.appendSql( " d," );
|
||||
value.accept( translator );
|
||||
sqlAppender.appendSql( " v from dual) t)" );
|
||||
|
||||
}
|
||||
}
|
|
@ -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.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_append function.
|
||||
*/
|
||||
public class PostgreSQLJsonArrayAppendFunction extends AbstractJsonArrayAppendFunction {
|
||||
|
||||
public PostgreSQLJsonArrayAppendFunction(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( "(select jsonb_set_lax(t.d,t.p,(t.d)#>t.p||" );
|
||||
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( ",false,'return_target') from (values(" );
|
||||
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() + 1 );
|
||||
sqlAppender.appendSql( '\'' );
|
||||
}
|
||||
separator = ',';
|
||||
}
|
||||
sqlAppender.appendSql( "]::text[]" );
|
||||
sqlAppender.appendSql( ")) t(d,p))" );
|
||||
}
|
||||
|
||||
private static boolean isJsonType(Expression expression) {
|
||||
final JdbcMappingContainer expressionType = expression.getExpressionType();
|
||||
return expressionType != null && expressionType.getSingleJdbcMapping().getJdbcType().isJson();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* SQL Server json_array_append function.
|
||||
*/
|
||||
public class SQLServerJsonArrayAppendFunction extends AbstractJsonArrayAppendFunction {
|
||||
|
||||
public SQLServerJsonArrayAppendFunction(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( "(select coalesce(" );
|
||||
sqlAppender.appendSql("case when json_modify(json_query(t.d,t.p),'append $',t.v) is not null then json_modify(t.d,t.p,json_modify(json_query(t.d,t.p),'append $',t.v)) end,");
|
||||
sqlAppender.appendSql("json_modify(t.d,t.p,json_query('['+coalesce(json_value(t.d,t.p),case when json_path_exists(t.d,t.p)=1 then 'null' end)+stuff(json_array(t.v),1,1,','))),");
|
||||
sqlAppender.appendSql( "t.d) from (values (" );
|
||||
json.accept( translator );
|
||||
sqlAppender.appendSql( ',' );
|
||||
jsonPath.accept( translator );
|
||||
sqlAppender.appendSql( ',' );
|
||||
renderValue( sqlAppender, value, translator );
|
||||
sqlAppender.appendSql( ")) t(d,p,v))" );
|
||||
}
|
||||
|
||||
protected void renderValue(SqlAppender sqlAppender, SqlAstNode value, SqlAstTranslator<?> translator) {
|
||||
if ( ExpressionTypeHelper.isBoolean( value ) ) {
|
||||
sqlAppender.appendSql( "cast(" );
|
||||
value.accept( translator );
|
||||
sqlAppender.appendSql( " as bit)" );
|
||||
}
|
||||
else {
|
||||
value.accept( translator );
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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.SupportsJsonArrayAppend.class)
|
||||
public class JsonArrayAppendTest {
|
||||
|
||||
@Test
|
||||
public void testSimple(SessionFactoryScope scope) {
|
||||
scope.inSession( em -> {
|
||||
//tag::hql-json-array-append-example[]
|
||||
em.createQuery( "select json_array_append('{\"a\":[1]}', '$.a', 2)" ).getResultList();
|
||||
//end::hql-json-array-append-example[]
|
||||
} );
|
||||
}
|
||||
|
||||
}
|
|
@ -640,6 +640,70 @@ public class JsonFunctionTests {
|
|||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsJsonArrayAppend.class)
|
||||
public void testJsonArrayAppend(SessionFactoryScope scope) {
|
||||
scope.inTransaction(
|
||||
session -> {
|
||||
String json = session.createQuery(
|
||||
"select json_array_append('{\"b\":[2]}', '$.b', 1)",
|
||||
String.class
|
||||
).getSingleResult();
|
||||
Map<String, Object> object = parseObject( json );
|
||||
assertEquals( 1, object.size() );
|
||||
assertEquals( Arrays.asList( 2, 1 ), object.get( "b" ) );
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsJsonArrayAppend.class)
|
||||
public void testJsonArrayAppendNonExisting(SessionFactoryScope scope) {
|
||||
scope.inTransaction(
|
||||
session -> {
|
||||
String json = session.createQuery(
|
||||
"select json_array_append('{\"b\":[2]}', '$.c', 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.SupportsJsonArrayAppend.class)
|
||||
public void testJsonArrayAppendNonArray(SessionFactoryScope scope) {
|
||||
scope.inTransaction(
|
||||
session -> {
|
||||
String json = session.createQuery(
|
||||
"select json_array_append('{\"b\":2}', '$.b', 1)",
|
||||
String.class
|
||||
).getSingleResult();
|
||||
Map<String, Object> object = parseObject( json );
|
||||
assertEquals( 1, object.size() );
|
||||
assertEquals( Arrays.asList( 2, 1 ), object.get( "b" ) );
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsJsonArrayAppend.class)
|
||||
public void testJsonArrayAppendToNull(SessionFactoryScope scope) {
|
||||
scope.inTransaction(
|
||||
session -> {
|
||||
String json = session.createQuery(
|
||||
"select json_array_append('{\"b\":null}', '$.b', 1)",
|
||||
String.class
|
||||
).getSingleResult();
|
||||
Map<String, Object> object = parseObject( json );
|
||||
assertEquals( 1, object.size() );
|
||||
assertEquals( Arrays.asList( null, 1 ), object.get( "b" ) );
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private static final ObjectMapper MAPPER = new ObjectMapper();
|
||||
|
||||
private static Map<String, Object> parseObject(String json) {
|
||||
|
|
|
@ -823,6 +823,12 @@ abstract public class DialectFeatureChecks {
|
|||
}
|
||||
}
|
||||
|
||||
public static class SupportsJsonArrayAppend implements DialectFeatureCheck {
|
||||
public boolean apply(Dialect dialect) {
|
||||
return definesFunction( dialect, "json_array_append" );
|
||||
}
|
||||
}
|
||||
|
||||
public static class IsJtds implements DialectFeatureCheck {
|
||||
public boolean apply(Dialect dialect) {
|
||||
return dialect instanceof SybaseDialect && ( (SybaseDialect) dialect ).getDriverKind() == SybaseDriverKind.JTDS;
|
||||
|
|
Loading…
Reference in New Issue