HHH-18604 Add json_set function
This commit is contained in:
parent
7e11d7a809
commit
17f328609a
|
@ -1639,6 +1639,7 @@ it is necessary to enable the `hibernate.query.hql.json_functions_enabled` confi
|
||||||
| `json_query()` | Queries non-scalar values by JSON path in a JSON document
|
| `json_query()` | Queries non-scalar values by JSON path in a JSON document
|
||||||
| `json_arrayagg()` | Creates a JSON array by aggregating values
|
| `json_arrayagg()` | Creates a JSON array by aggregating values
|
||||||
| `json_objectagg()` | Creates a JSON object by aggregating values
|
| `json_objectagg()` | Creates a JSON object by aggregating values
|
||||||
|
| `json_set()` | Inserts/Replaces a value by JSON path within a JSON document
|
||||||
|===
|
|===
|
||||||
|
|
||||||
|
|
||||||
|
@ -2013,6 +2014,22 @@ include::{json-example-dir-hql}/JsonObjectAggregateTest.java[tags=hql-json-objec
|
||||||
|
|
||||||
WARNING: Some databases like e.g. MySQL, SAP HANA, DB2 and SQL Server do not support raising an error on duplicate keys.
|
WARNING: Some databases like e.g. MySQL, SAP HANA, DB2 and SQL Server do not support raising an error on duplicate keys.
|
||||||
|
|
||||||
|
[[hql-json-set-function]]
|
||||||
|
===== `json_set()`
|
||||||
|
|
||||||
|
Inserts/Replaces a value by JSON path within a JSON document.
|
||||||
|
The function takes 3 arguments, the json document, the json path and the new value to set/insert.
|
||||||
|
|
||||||
|
[[hql-json-set-example]]
|
||||||
|
====
|
||||||
|
[source, java, indent=0]
|
||||||
|
----
|
||||||
|
include::{json-example-dir-hql}/JsonSetTest.java[tags=hql-json-set-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
|
||||||
|
|
||||||
|
|
|
@ -508,6 +508,7 @@ public class CockroachLegacyDialect extends Dialect {
|
||||||
functionFactory.jsonArray_postgresql();
|
functionFactory.jsonArray_postgresql();
|
||||||
functionFactory.jsonArrayAgg_postgresql( false );
|
functionFactory.jsonArrayAgg_postgresql( false );
|
||||||
functionFactory.jsonObjectAgg_postgresql( false );
|
functionFactory.jsonObjectAgg_postgresql( false );
|
||||||
|
functionFactory.jsonSet_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)" )
|
||||||
|
|
|
@ -660,6 +660,7 @@ public class MySQLLegacyDialect extends Dialect {
|
||||||
functionFactory.jsonArray_mysql();
|
functionFactory.jsonArray_mysql();
|
||||||
functionFactory.jsonArrayAgg_mysql();
|
functionFactory.jsonArrayAgg_mysql();
|
||||||
functionFactory.jsonObjectAgg_mysql();
|
functionFactory.jsonObjectAgg_mysql();
|
||||||
|
functionFactory.jsonSet_mysql();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -316,6 +316,7 @@ public class OracleLegacyDialect extends Dialect {
|
||||||
functionFactory.jsonObject_oracle();
|
functionFactory.jsonObject_oracle();
|
||||||
functionFactory.jsonArray_oracle();
|
functionFactory.jsonArray_oracle();
|
||||||
functionFactory.jsonArrayAgg_oracle();
|
functionFactory.jsonArrayAgg_oracle();
|
||||||
|
functionFactory.jsonSet_oracle();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -658,6 +658,7 @@ public class PostgreSQLLegacyDialect extends Dialect {
|
||||||
functionFactory.jsonObjectAgg_postgresql( false );
|
functionFactory.jsonObjectAgg_postgresql( false );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
functionFactory.jsonSet_postgresql();
|
||||||
|
|
||||||
if ( getVersion().isSameOrAfter( 9, 4 ) ) {
|
if ( getVersion().isSameOrAfter( 9, 4 ) ) {
|
||||||
functionFactory.makeDateTimeTimestamp();
|
functionFactory.makeDateTimeTimestamp();
|
||||||
|
|
|
@ -406,6 +406,7 @@ public class SQLServerLegacyDialect extends AbstractTransactSQLDialect {
|
||||||
functionFactory.jsonExists_sqlserver();
|
functionFactory.jsonExists_sqlserver();
|
||||||
functionFactory.jsonObject_sqlserver();
|
functionFactory.jsonObject_sqlserver();
|
||||||
functionFactory.jsonArray_sqlserver();
|
functionFactory.jsonArray_sqlserver();
|
||||||
|
functionFactory.jsonSet_sqlserver();
|
||||||
}
|
}
|
||||||
if ( getVersion().isSameOrAfter( 14 ) ) {
|
if ( getVersion().isSameOrAfter( 14 ) ) {
|
||||||
functionFactory.listagg_stringAggWithinGroup( "varchar(max)" );
|
functionFactory.listagg_stringAggWithinGroup( "varchar(max)" );
|
||||||
|
|
|
@ -475,6 +475,7 @@ public class CockroachDialect extends Dialect {
|
||||||
functionFactory.jsonArray_postgresql();
|
functionFactory.jsonArray_postgresql();
|
||||||
functionFactory.jsonArrayAgg_postgresql( false );
|
functionFactory.jsonArrayAgg_postgresql( false );
|
||||||
functionFactory.jsonObjectAgg_postgresql( false );
|
functionFactory.jsonObjectAgg_postgresql( false );
|
||||||
|
functionFactory.jsonSet_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)" )
|
||||||
|
|
|
@ -645,6 +645,7 @@ public class MySQLDialect extends Dialect {
|
||||||
functionFactory.jsonArray_mysql();
|
functionFactory.jsonArray_mysql();
|
||||||
functionFactory.jsonArrayAgg_mysql();
|
functionFactory.jsonArrayAgg_mysql();
|
||||||
functionFactory.jsonObjectAgg_mysql();
|
functionFactory.jsonObjectAgg_mysql();
|
||||||
|
functionFactory.jsonSet_mysql();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -407,6 +407,7 @@ public class OracleDialect extends Dialect {
|
||||||
functionFactory.jsonObject_oracle();
|
functionFactory.jsonObject_oracle();
|
||||||
functionFactory.jsonArray_oracle();
|
functionFactory.jsonArray_oracle();
|
||||||
functionFactory.jsonArrayAgg_oracle();
|
functionFactory.jsonArrayAgg_oracle();
|
||||||
|
functionFactory.jsonSet_oracle();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -619,6 +619,7 @@ public class PostgreSQLDialect extends Dialect {
|
||||||
functionFactory.jsonObjectAgg_postgresql( false );
|
functionFactory.jsonObjectAgg_postgresql( false );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
functionFactory.jsonSet_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
|
||||||
|
|
|
@ -424,6 +424,7 @@ public class SQLServerDialect extends AbstractTransactSQLDialect {
|
||||||
functionFactory.jsonExists_sqlserver();
|
functionFactory.jsonExists_sqlserver();
|
||||||
functionFactory.jsonObject_sqlserver();
|
functionFactory.jsonObject_sqlserver();
|
||||||
functionFactory.jsonArray_sqlserver();
|
functionFactory.jsonArray_sqlserver();
|
||||||
|
functionFactory.jsonSet_sqlserver();
|
||||||
}
|
}
|
||||||
if ( getVersion().isSameOrAfter( 14 ) ) {
|
if ( getVersion().isSameOrAfter( 14 ) ) {
|
||||||
functionFactory.listagg_stringAggWithinGroup( "varchar(max)" );
|
functionFactory.listagg_stringAggWithinGroup( "varchar(max)" );
|
||||||
|
|
|
@ -118,12 +118,14 @@ 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.OracleJsonArrayFunction;
|
import org.hibernate.dialect.function.json.OracleJsonArrayFunction;
|
||||||
import org.hibernate.dialect.function.json.OracleJsonObjectFunction;
|
import org.hibernate.dialect.function.json.OracleJsonObjectFunction;
|
||||||
|
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.PostgreSQLJsonArrayFunction;
|
import org.hibernate.dialect.function.json.PostgreSQLJsonArrayFunction;
|
||||||
import org.hibernate.dialect.function.json.PostgreSQLJsonExistsFunction;
|
import org.hibernate.dialect.function.json.PostgreSQLJsonExistsFunction;
|
||||||
import org.hibernate.dialect.function.json.PostgreSQLJsonObjectAggFunction;
|
import org.hibernate.dialect.function.json.PostgreSQLJsonObjectAggFunction;
|
||||||
import org.hibernate.dialect.function.json.PostgreSQLJsonObjectFunction;
|
import org.hibernate.dialect.function.json.PostgreSQLJsonObjectFunction;
|
||||||
import org.hibernate.dialect.function.json.PostgreSQLJsonQueryFunction;
|
import org.hibernate.dialect.function.json.PostgreSQLJsonQueryFunction;
|
||||||
|
import org.hibernate.dialect.function.json.PostgreSQLJsonSetFunction;
|
||||||
import org.hibernate.dialect.function.json.PostgreSQLJsonValueFunction;
|
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.SQLServerJsonArrayFunction;
|
import org.hibernate.dialect.function.json.SQLServerJsonArrayFunction;
|
||||||
|
@ -131,15 +133,18 @@ import org.hibernate.dialect.function.json.SQLServerJsonExistsFunction;
|
||||||
import org.hibernate.dialect.function.json.SQLServerJsonObjectAggFunction;
|
import org.hibernate.dialect.function.json.SQLServerJsonObjectAggFunction;
|
||||||
import org.hibernate.dialect.function.json.SQLServerJsonObjectFunction;
|
import org.hibernate.dialect.function.json.SQLServerJsonObjectFunction;
|
||||||
import org.hibernate.dialect.function.json.SQLServerJsonQueryFunction;
|
import org.hibernate.dialect.function.json.SQLServerJsonQueryFunction;
|
||||||
|
import org.hibernate.dialect.function.json.SQLServerJsonSetFunction;
|
||||||
import org.hibernate.dialect.function.json.SQLServerJsonValueFunction;
|
import org.hibernate.dialect.function.json.SQLServerJsonValueFunction;
|
||||||
import org.hibernate.query.sqm.function.SqmFunctionRegistry;
|
import org.hibernate.query.sqm.function.SqmFunctionRegistry;
|
||||||
import org.hibernate.query.sqm.produce.function.ArgumentTypesValidator;
|
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.StandardArgumentsValidators;
|
||||||
import org.hibernate.query.sqm.produce.function.StandardFunctionArgumentTypeResolvers;
|
import org.hibernate.query.sqm.produce.function.StandardFunctionArgumentTypeResolvers;
|
||||||
import org.hibernate.query.sqm.produce.function.StandardFunctionReturnTypeResolvers;
|
import org.hibernate.query.sqm.produce.function.StandardFunctionReturnTypeResolvers;
|
||||||
import org.hibernate.sql.ast.SqlAstNodeRenderingMode;
|
import org.hibernate.sql.ast.SqlAstNodeRenderingMode;
|
||||||
import org.hibernate.type.BasicType;
|
import org.hibernate.type.BasicType;
|
||||||
import org.hibernate.type.BasicTypeRegistry;
|
import org.hibernate.type.BasicTypeRegistry;
|
||||||
|
import org.hibernate.type.SqlTypes;
|
||||||
import org.hibernate.type.StandardBasicTypes;
|
import org.hibernate.type.StandardBasicTypes;
|
||||||
import org.hibernate.type.spi.TypeConfiguration;
|
import org.hibernate.type.spi.TypeConfiguration;
|
||||||
|
|
||||||
|
@ -3796,4 +3801,42 @@ public class CommonFunctionFactory {
|
||||||
public void jsonObjectAgg_db2() {
|
public void jsonObjectAgg_db2() {
|
||||||
functionRegistry.register( "json_objectagg", new DB2JsonObjectAggFunction( typeConfiguration ) );
|
functionRegistry.register( "json_objectagg", new DB2JsonObjectAggFunction( typeConfiguration ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PostgreSQL json_set() function
|
||||||
|
*/
|
||||||
|
public void jsonSet_postgresql() {
|
||||||
|
functionRegistry.register( "json_set", new PostgreSQLJsonSetFunction( typeConfiguration ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MySQL json_set() function
|
||||||
|
*/
|
||||||
|
public void jsonSet_mysql() {
|
||||||
|
functionRegistry.namedDescriptorBuilder( "json_set" )
|
||||||
|
.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_set() function
|
||||||
|
*/
|
||||||
|
public void jsonSet_oracle() {
|
||||||
|
functionRegistry.register( "json_set", new OracleJsonSetFunction( typeConfiguration ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SQL Server json_set() function
|
||||||
|
*/
|
||||||
|
public void jsonSet_sqlserver() {
|
||||||
|
functionRegistry.register( "json_set", new SQLServerJsonSetFunction( typeConfiguration ) );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,40 @@
|
||||||
|
/*
|
||||||
|
* Hibernate, Relational Persistence for Idiomatic Java
|
||||||
|
*
|
||||||
|
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
|
||||||
|
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
|
||||||
|
*/
|
||||||
|
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_set function.
|
||||||
|
*/
|
||||||
|
public abstract class AbstractJsonSetFunction extends AbstractSqmSelfRenderingFunctionDescriptor {
|
||||||
|
|
||||||
|
public AbstractJsonSetFunction(TypeConfiguration typeConfiguration) {
|
||||||
|
super(
|
||||||
|
"json_set",
|
||||||
|
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,44 @@
|
||||||
|
/*
|
||||||
|
* Hibernate, Relational Persistence for Idiomatic Java
|
||||||
|
*
|
||||||
|
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
|
||||||
|
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
|
||||||
|
*/
|
||||||
|
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_set function.
|
||||||
|
*/
|
||||||
|
public class OracleJsonSetFunction extends AbstractJsonSetFunction {
|
||||||
|
|
||||||
|
public OracleJsonSetFunction(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_transform(" );
|
||||||
|
json.accept( translator );
|
||||||
|
sqlAppender.appendSql( ",set " );
|
||||||
|
jsonPath.accept( translator );
|
||||||
|
sqlAppender.appendSql( '=' );
|
||||||
|
value.accept( translator );
|
||||||
|
sqlAppender.appendSql( ')' );
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,90 @@
|
||||||
|
/*
|
||||||
|
* Hibernate, Relational Persistence for Idiomatic Java
|
||||||
|
*
|
||||||
|
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
|
||||||
|
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
|
||||||
|
*/
|
||||||
|
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.JdbcParameter;
|
||||||
|
import org.hibernate.sql.ast.tree.expression.Literal;
|
||||||
|
import org.hibernate.type.spi.TypeConfiguration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PostgreSQL json_set function.
|
||||||
|
*/
|
||||||
|
public class PostgreSQLJsonSetFunction extends AbstractJsonSetFunction {
|
||||||
|
|
||||||
|
public PostgreSQLJsonSetFunction(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_set(" );
|
||||||
|
final boolean needsCast = !isJsonType( json ) && json instanceof JdbcParameter;
|
||||||
|
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( ",true)" );
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isJsonType(Expression expression) {
|
||||||
|
final JdbcMappingContainer expressionType = expression.getExpressionType();
|
||||||
|
return expressionType != null && expressionType.getSingleJdbcMapping().getJdbcType().isJson();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,44 @@
|
||||||
|
/*
|
||||||
|
* Hibernate, Relational Persistence for Idiomatic Java
|
||||||
|
*
|
||||||
|
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
|
||||||
|
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
|
||||||
|
*/
|
||||||
|
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_set function.
|
||||||
|
*/
|
||||||
|
public class SQLServerJsonSetFunction extends AbstractJsonSetFunction {
|
||||||
|
|
||||||
|
public SQLServerJsonSetFunction(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_modify(" );
|
||||||
|
json.accept( translator );
|
||||||
|
sqlAppender.appendSql( ',' );
|
||||||
|
jsonPath.accept( translator );
|
||||||
|
sqlAppender.appendSql( ',' );
|
||||||
|
value.accept( translator );
|
||||||
|
sqlAppender.appendSql( ')' );
|
||||||
|
}
|
||||||
|
}
|
|
@ -3911,6 +3911,38 @@ public interface HibernateCriteriaBuilder extends CriteriaBuilder {
|
||||||
@Incubating
|
@Incubating
|
||||||
JpaExpression<String> jsonObjectAggWithUniqueKeysAndNulls(Expression<?> key, Expression<?> value, Predicate filter);
|
JpaExpression<String> jsonObjectAggWithUniqueKeysAndNulls(Expression<?> key, Expression<?> value, Predicate filter);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inserts/Replaces a value by JSON path within a JSON document.
|
||||||
|
*
|
||||||
|
* @since 7.0
|
||||||
|
*/
|
||||||
|
@Incubating
|
||||||
|
JpaExpression<String> jsonSet(Expression<?> jsonDocument, String jsonPath, Expression<?> value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inserts/Replaces a value by JSON path within a JSON document.
|
||||||
|
*
|
||||||
|
* @since 7.0
|
||||||
|
*/
|
||||||
|
@Incubating
|
||||||
|
JpaExpression<String> jsonSet(Expression<?> jsonDocument, Expression<String> jsonPath, Expression<?> value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inserts/Replaces a value by JSON path within a JSON document.
|
||||||
|
*
|
||||||
|
* @since 7.0
|
||||||
|
*/
|
||||||
|
@Incubating
|
||||||
|
JpaExpression<String> jsonSet(Expression<?> jsonDocument, String jsonPath, Object value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inserts/Replaces a value by JSON path within a JSON document.
|
||||||
|
*
|
||||||
|
* @since 7.0
|
||||||
|
*/
|
||||||
|
@Incubating
|
||||||
|
JpaExpression<String> jsonSet(Expression<?> jsonDocument, Expression<String> jsonPath, Object value);
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
JpaPredicate and(List<Predicate> restrictions);
|
JpaPredicate and(List<Predicate> restrictions);
|
||||||
|
|
||||||
|
|
|
@ -3523,4 +3523,28 @@ public class HibernateCriteriaBuilderDelegate implements HibernateCriteriaBuilde
|
||||||
Predicate filter) {
|
Predicate filter) {
|
||||||
return criteriaBuilder.jsonObjectAggWithUniqueKeysAndNulls( key, value, filter );
|
return criteriaBuilder.jsonObjectAggWithUniqueKeysAndNulls( key, value, filter );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Incubating
|
||||||
|
public JpaExpression<String> jsonSet(Expression<?> jsonDocument, String jsonPath, Expression<?> value) {
|
||||||
|
return criteriaBuilder.jsonSet( jsonDocument, jsonPath, value );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Incubating
|
||||||
|
public JpaExpression<String> jsonSet(Expression<?> jsonDocument, Expression<String> jsonPath, Expression<?> value) {
|
||||||
|
return criteriaBuilder.jsonSet( jsonDocument, jsonPath, value );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Incubating
|
||||||
|
public JpaExpression<String> jsonSet(Expression<?> jsonDocument, String jsonPath, Object value) {
|
||||||
|
return criteriaBuilder.jsonSet( jsonDocument, jsonPath, value );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Incubating
|
||||||
|
public JpaExpression<String> jsonSet(Expression<?> jsonDocument, Expression<String> jsonPath, Object value) {
|
||||||
|
return criteriaBuilder.jsonSet( jsonDocument, jsonPath, value );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -703,6 +703,18 @@ public interface NodeBuilder extends HibernateCriteriaBuilder, BindingContext {
|
||||||
@Override
|
@Override
|
||||||
SqmExpression<String> jsonObjectAgg(Expression<?> key, Expression<?> value, Predicate filter);
|
SqmExpression<String> jsonObjectAgg(Expression<?> key, Expression<?> value, Predicate filter);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
SqmExpression<String> jsonSet(Expression<?> jsonDocument, Expression<String> jsonPath, Object value);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
SqmExpression<String> jsonSet(Expression<?> jsonDocument, String jsonPath, Object value);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
SqmExpression<String> jsonSet(Expression<?> jsonDocument, Expression<String> jsonPath, Expression<?> value);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
SqmExpression<String> jsonSet(Expression<?> jsonDocument, String jsonPath, Expression<?> value);
|
||||||
|
|
||||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
// Covariant overrides
|
// Covariant overrides
|
||||||
|
|
||||||
|
|
|
@ -5555,4 +5555,29 @@ public class SqmCriteriaNodeBuilder implements NodeBuilder, Serializable {
|
||||||
}
|
}
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SqmExpression<String> jsonSet(Expression<?> jsonDocument, Expression<String> jsonPath, Object value) {
|
||||||
|
return jsonSet( jsonDocument, jsonPath, value( value ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SqmExpression<String> jsonSet(Expression<?> jsonDocument, String jsonPath, Object value) {
|
||||||
|
return jsonSet( jsonDocument, value( jsonPath ), value( value ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SqmExpression<String> jsonSet(Expression<?> jsonDocument, String jsonPath, Expression<?> value) {
|
||||||
|
return jsonSet( jsonDocument, value( jsonPath ), value );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SqmExpression<String> jsonSet(Expression<?> jsonDocument, Expression<String> jsonPath, Expression<?> value) {
|
||||||
|
//noinspection unchecked
|
||||||
|
return getFunctionDescriptor( "json_set" ).generateSqmExpression(
|
||||||
|
(List<? extends SqmTypedNode<?>>) (List<?>) asList( jsonDocument, jsonPath, value ),
|
||||||
|
null,
|
||||||
|
queryEngine
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,46 @@
|
||||||
|
/*
|
||||||
|
* Hibernate, Relational Persistence for Idiomatic Java
|
||||||
|
*
|
||||||
|
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||||
|
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
|
||||||
|
*/
|
||||||
|
package org.hibernate.orm.test.function.json;
|
||||||
|
|
||||||
|
import org.hibernate.cfg.QuerySettings;
|
||||||
|
import org.hibernate.dialect.CockroachDialect;
|
||||||
|
import org.hibernate.dialect.DB2Dialect;
|
||||||
|
import org.hibernate.dialect.HANADialect;
|
||||||
|
import org.hibernate.dialect.MySQLDialect;
|
||||||
|
import org.hibernate.dialect.PostgreSQLDialect;
|
||||||
|
import org.hibernate.dialect.SQLServerDialect;
|
||||||
|
|
||||||
|
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.hibernate.testing.orm.junit.SkipForDialect;
|
||||||
|
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.SupportsJsonSet.class)
|
||||||
|
public class JsonSetTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSimple(SessionFactoryScope scope) {
|
||||||
|
scope.inSession( em -> {
|
||||||
|
//tag::hql-json-set-example[]
|
||||||
|
em.createQuery( "select json_set('{\"a\":1}', '$.a', 2)" ).getResultList();
|
||||||
|
//end::hql-json-set-example[]
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -466,6 +466,38 @@ public class JsonFunctionTests {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsJsonSet.class)
|
||||||
|
public void testJsonSet(SessionFactoryScope scope) {
|
||||||
|
scope.inTransaction(
|
||||||
|
session -> {
|
||||||
|
String json = session.createQuery(
|
||||||
|
"select json_set('{}', '$.a', 123)",
|
||||||
|
String.class
|
||||||
|
).getSingleResult();
|
||||||
|
Map<String, Object> object = parseObject( json );
|
||||||
|
assertEquals( 1, object.size() );
|
||||||
|
assertEquals( 123, object.get( "a" ) );
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsJsonSet.class)
|
||||||
|
public void testJsonSetReplace(SessionFactoryScope scope) {
|
||||||
|
scope.inTransaction(
|
||||||
|
session -> {
|
||||||
|
String json = session.createQuery(
|
||||||
|
"select json_set('{\"a\":456}', '$.a', 123)",
|
||||||
|
String.class
|
||||||
|
).getSingleResult();
|
||||||
|
Map<String, Object> object = parseObject( json );
|
||||||
|
assertEquals( 1, object.size() );
|
||||||
|
assertEquals( 123, object.get( "a" ) );
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
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) {
|
||||||
|
|
|
@ -793,6 +793,12 @@ abstract public class DialectFeatureChecks {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class SupportsJsonSet implements DialectFeatureCheck {
|
||||||
|
public boolean apply(Dialect dialect) {
|
||||||
|
return definesFunction( dialect, "json_set" );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
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