HHH-18604 Add json_remove function and fix some Oracle functions

This commit is contained in:
Christian Beikov 2024-09-16 17:27:17 +02:00
parent 17f328609a
commit 36066a03e6
27 changed files with 527 additions and 4 deletions

View File

@ -1640,6 +1640,7 @@ it is necessary to enable the `hibernate.query.hql.json_functions_enabled` confi
| `json_arrayagg()` | Creates a JSON array 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
| `json_remove()` | Removes a value by JSON path within a JSON document
|===
@ -2030,6 +2031,22 @@ 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-json-remove-function]]
===== `json_remove()`
Removes a value by JSON path within a JSON document.
The function takes 2 arguments, the json document and the json path representing what to remove.
[[hql-json-remove-example]]
====
[source, java, indent=0]
----
include::{json-example-dir-hql}/JsonRemoveTest.java[tags=hql-json-remove-example]
----
====
WARNING: SAP HANA, DB2, H2 and HSQLDB do not support this function.
[[hql-user-defined-functions]]
==== Native and user-defined functions

View File

@ -509,6 +509,7 @@ public class CockroachLegacyDialect extends Dialect {
functionFactory.jsonArrayAgg_postgresql( false );
functionFactory.jsonObjectAgg_postgresql( false );
functionFactory.jsonSet_postgresql();
functionFactory.jsonRemove_postgresql();
// Postgres uses # instead of ^ for XOR
functionContributions.getFunctionRegistry().patternDescriptorBuilder( "bitxor", "(?1#?2)" )

View File

@ -661,6 +661,7 @@ public class MySQLLegacyDialect extends Dialect {
functionFactory.jsonArrayAgg_mysql();
functionFactory.jsonObjectAgg_mysql();
functionFactory.jsonSet_mysql();
functionFactory.jsonRemove_mysql();
}
}

View File

@ -316,7 +316,9 @@ public class OracleLegacyDialect extends Dialect {
functionFactory.jsonObject_oracle();
functionFactory.jsonArray_oracle();
functionFactory.jsonArrayAgg_oracle();
functionFactory.jsonObjectAgg_oracle();
functionFactory.jsonSet_oracle();
functionFactory.jsonRemove_oracle();
}
}

View File

@ -659,6 +659,7 @@ public class PostgreSQLLegacyDialect extends Dialect {
}
}
functionFactory.jsonSet_postgresql();
functionFactory.jsonRemove_postgresql();
if ( getVersion().isSameOrAfter( 9, 4 ) ) {
functionFactory.makeDateTimeTimestamp();

View File

@ -407,6 +407,7 @@ public class SQLServerLegacyDialect extends AbstractTransactSQLDialect {
functionFactory.jsonObject_sqlserver();
functionFactory.jsonArray_sqlserver();
functionFactory.jsonSet_sqlserver();
functionFactory.jsonRemove_sqlserver();
}
if ( getVersion().isSameOrAfter( 14 ) ) {
functionFactory.listagg_stringAggWithinGroup( "varchar(max)" );

View File

@ -476,6 +476,7 @@ public class CockroachDialect extends Dialect {
functionFactory.jsonArrayAgg_postgresql( false );
functionFactory.jsonObjectAgg_postgresql( false );
functionFactory.jsonSet_postgresql();
functionFactory.jsonRemove_postgresql();
// Postgres uses # instead of ^ for XOR
functionContributions.getFunctionRegistry().patternDescriptorBuilder( "bitxor", "(?1#?2)" )

View File

@ -646,6 +646,7 @@ public class MySQLDialect extends Dialect {
functionFactory.jsonArrayAgg_mysql();
functionFactory.jsonObjectAgg_mysql();
functionFactory.jsonSet_mysql();
functionFactory.jsonRemove_mysql();
}
@Override

View File

@ -407,7 +407,9 @@ public class OracleDialect extends Dialect {
functionFactory.jsonObject_oracle();
functionFactory.jsonArray_oracle();
functionFactory.jsonArrayAgg_oracle();
functionFactory.jsonObjectAgg_oracle();
functionFactory.jsonSet_oracle();
functionFactory.jsonRemove_oracle();
}
@Override

View File

@ -620,6 +620,7 @@ public class PostgreSQLDialect extends Dialect {
}
}
functionFactory.jsonSet_postgresql();
functionFactory.jsonRemove_postgresql();
functionFactory.makeDateTimeTimestamp();
// Note that PostgreSQL doesn't support the OVER clause for ordered set-aggregate functions

View File

@ -425,6 +425,7 @@ public class SQLServerDialect extends AbstractTransactSQLDialect {
functionFactory.jsonObject_sqlserver();
functionFactory.jsonArray_sqlserver();
functionFactory.jsonSet_sqlserver();
functionFactory.jsonRemove_sqlserver();
}
if ( getVersion().isSameOrAfter( 14 ) ) {
functionFactory.listagg_stringAggWithinGroup( "varchar(max)" );

View File

@ -117,7 +117,9 @@ 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.OracleJsonArrayFunction;
import org.hibernate.dialect.function.json.OracleJsonObjectAggFunction;
import org.hibernate.dialect.function.json.OracleJsonObjectFunction;
import org.hibernate.dialect.function.json.OracleJsonRemoveFunction;
import org.hibernate.dialect.function.json.OracleJsonSetFunction;
import org.hibernate.dialect.function.json.PostgreSQLJsonArrayAggFunction;
import org.hibernate.dialect.function.json.PostgreSQLJsonArrayFunction;
@ -125,6 +127,7 @@ import org.hibernate.dialect.function.json.PostgreSQLJsonExistsFunction;
import org.hibernate.dialect.function.json.PostgreSQLJsonObjectAggFunction;
import org.hibernate.dialect.function.json.PostgreSQLJsonObjectFunction;
import org.hibernate.dialect.function.json.PostgreSQLJsonQueryFunction;
import org.hibernate.dialect.function.json.PostgreSQLJsonRemoveFunction;
import org.hibernate.dialect.function.json.PostgreSQLJsonSetFunction;
import org.hibernate.dialect.function.json.PostgreSQLJsonValueFunction;
import org.hibernate.dialect.function.json.SQLServerJsonArrayAggFunction;
@ -133,6 +136,7 @@ import org.hibernate.dialect.function.json.SQLServerJsonExistsFunction;
import org.hibernate.dialect.function.json.SQLServerJsonObjectAggFunction;
import org.hibernate.dialect.function.json.SQLServerJsonObjectFunction;
import org.hibernate.dialect.function.json.SQLServerJsonQueryFunction;
import org.hibernate.dialect.function.json.SQLServerJsonRemoveFunction;
import org.hibernate.dialect.function.json.SQLServerJsonSetFunction;
import org.hibernate.dialect.function.json.SQLServerJsonValueFunction;
import org.hibernate.query.sqm.function.SqmFunctionRegistry;
@ -3753,6 +3757,13 @@ public class CommonFunctionFactory {
functionRegistry.register( "json_arrayagg", new HANAJsonArrayAggFunction( typeConfiguration ) );
}
/**
* Oracle json_objectagg() function
*/
public void jsonObjectAgg_oracle() {
functionRegistry.register( "json_objectagg", new OracleJsonObjectAggFunction( typeConfiguration ) );
}
/**
* json_objectagg() function for H2 and HSQLDB
*/
@ -3839,4 +3850,41 @@ public class CommonFunctionFactory {
public void jsonSet_sqlserver() {
functionRegistry.register( "json_set", new SQLServerJsonSetFunction( typeConfiguration ) );
}
/**
* PostgreSQL json_remove() function
*/
public void jsonRemove_postgresql() {
functionRegistry.register( "json_remove", new PostgreSQLJsonRemoveFunction( typeConfiguration ) );
}
/**
* MySQL json_remove() function
*/
public void jsonRemove_mysql() {
functionRegistry.namedDescriptorBuilder( "json_remove" )
.setArgumentsValidator( new ArgumentTypesValidator(
StandardArgumentsValidators.exactly( 2 ),
FunctionParameterType.IMPLICIT_JSON,
FunctionParameterType.STRING
) )
.setReturnTypeResolver( StandardFunctionReturnTypeResolvers.invariant(
typeConfiguration.getBasicTypeRegistry().resolve( String.class, SqlTypes.JSON )
) )
.register();
}
/**
* Oracle json_remove() function
*/
public void jsonRemove_oracle() {
functionRegistry.register( "json_remove", new OracleJsonRemoveFunction( typeConfiguration ) );
}
/**
* SQL server json_remove() function
*/
public void jsonRemove_sqlserver() {
functionRegistry.register( "json_remove", new SQLServerJsonRemoveFunction( typeConfiguration ) );
}
}

View File

@ -0,0 +1,39 @@
/*
* 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_remove function.
*/
public abstract class AbstractJsonRemoveFunction extends AbstractSqmSelfRenderingFunctionDescriptor {
public AbstractJsonRemoveFunction(TypeConfiguration typeConfiguration) {
super(
"json_remove",
FunctionKind.NORMAL,
new ArgumentTypesValidator(
StandardArgumentsValidators.exactly( 2 ),
FunctionParameterType.IMPLICIT_JSON,
FunctionParameterType.STRING
),
StandardFunctionReturnTypeResolvers.invariant(
typeConfiguration.getBasicTypeRegistry().resolve( String.class, SqlTypes.JSON )
),
null
);
}
}

View File

@ -6,9 +6,14 @@
*/
package org.hibernate.dialect.function.json;
import java.util.List;
import org.hibernate.dialect.function.CastFunction;
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.expression.CastTarget;
import org.hibernate.sql.ast.tree.expression.Expression;
import org.hibernate.sql.ast.tree.expression.JsonNullBehavior;
import org.hibernate.type.SqlTypes;
@ -19,8 +24,12 @@ import org.hibernate.type.spi.TypeConfiguration;
*/
public class OracleJsonArrayAggFunction extends JsonArrayAggFunction {
private final CastTarget stringCastTarget;
private CastFunction castFunction;
public OracleJsonArrayAggFunction(TypeConfiguration typeConfiguration) {
super( false, typeConfiguration );
this.stringCastTarget = new CastTarget( typeConfiguration.getBasicTypeForJavaType( String.class ) );
}
@Override
@ -29,11 +38,29 @@ public class OracleJsonArrayAggFunction extends JsonArrayAggFunction {
Expression arg,
JsonNullBehavior nullBehavior,
SqlAstTranslator<?> translator) {
arg.accept( translator );
final JdbcMappingContainer expressionType = arg.getExpressionType();
if ( expressionType != null && expressionType.getSingleJdbcMapping().getJdbcType().isJson()
&& !SqlTypes.isJsonType( expressionType.getSingleJdbcMapping().getJdbcType().getDdlTypeCode() ) ) {
if ( ExpressionTypeHelper.isNonNativeBoolean( arg ) ) {
CastFunction castFunction = this.castFunction;
if ( castFunction == null ) {
castFunction = this.castFunction = (CastFunction) translator.getSessionFactory()
.getQueryEngine()
.getSqmFunctionRegistry()
.findFunctionDescriptor( "cast" );
}
castFunction.render(
sqlAppender,
List.of( arg, stringCastTarget ),
(ReturnableType<?>) stringCastTarget.getJdbcMapping(),
translator
);
sqlAppender.appendSql( " format json" );
}
else {
arg.accept( translator );
final JdbcMappingContainer expressionType = arg.getExpressionType();
if ( expressionType != null && expressionType.getSingleJdbcMapping().getJdbcType().isJson()
&& !SqlTypes.isJsonType( expressionType.getSingleJdbcMapping().getJdbcType().getDdlTypeCode() ) ) {
sqlAppender.appendSql( " format json" );
}
}
}
}

View File

@ -9,11 +9,14 @@ package org.hibernate.dialect.function.json;
import java.util.List;
import org.hibernate.dialect.function.CastFunction;
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.CastTarget;
import org.hibernate.sql.ast.tree.expression.Expression;
import org.hibernate.type.SqlTypes;
import org.hibernate.type.spi.TypeConfiguration;
/**
@ -49,6 +52,11 @@ public class OracleJsonArrayFunction extends JsonArrayFunction {
}
else {
value.accept( walker );
final JdbcMappingContainer expressionType = ( (Expression) value ).getExpressionType();
if ( expressionType != null && expressionType.getSingleJdbcMapping().getJdbcType().isJson()
&& !SqlTypes.isJsonType( expressionType.getSingleJdbcMapping().getJdbcType().getDdlTypeCode() ) ) {
sqlAppender.appendSql( " format json" );
}
}
}
}

View File

@ -0,0 +1,66 @@
/*
* 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.dialect.function.CastFunction;
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.expression.CastTarget;
import org.hibernate.sql.ast.tree.expression.Expression;
import org.hibernate.sql.ast.tree.expression.JsonNullBehavior;
import org.hibernate.type.SqlTypes;
import org.hibernate.type.spi.TypeConfiguration;
/**
* Oracle json_objectagg function.
*/
public class OracleJsonObjectAggFunction extends JsonObjectAggFunction {
private final CastTarget stringCastTarget;
private CastFunction castFunction;
public OracleJsonObjectAggFunction(TypeConfiguration typeConfiguration) {
super( " value ", false, typeConfiguration );
this.stringCastTarget = new CastTarget( typeConfiguration.getBasicTypeForJavaType( String.class ) );
}
@Override
protected void renderArgument(
SqlAppender sqlAppender,
Expression arg,
JsonNullBehavior nullBehavior,
SqlAstTranslator<?> translator) {
if ( ExpressionTypeHelper.isNonNativeBoolean( arg ) ) {
CastFunction castFunction = this.castFunction;
if ( castFunction == null ) {
castFunction = this.castFunction = (CastFunction) translator.getSessionFactory()
.getQueryEngine()
.getSqmFunctionRegistry()
.findFunctionDescriptor( "cast" );
}
castFunction.render(
sqlAppender,
List.of( arg, stringCastTarget ),
(ReturnableType<?>) stringCastTarget.getJdbcMapping(),
translator
);
sqlAppender.appendSql( " format json" );
}
else {
arg.accept( translator );
final JdbcMappingContainer expressionType = arg.getExpressionType();
if ( expressionType != null && expressionType.getSingleJdbcMapping().getJdbcType().isJson()
&& !SqlTypes.isJsonType( expressionType.getSingleJdbcMapping().getJdbcType().getDdlTypeCode() ) ) {
sqlAppender.appendSql( " format json" );
}
}
}
}

View File

@ -9,11 +9,14 @@ package org.hibernate.dialect.function.json;
import java.util.List;
import org.hibernate.dialect.function.CastFunction;
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.CastTarget;
import org.hibernate.sql.ast.tree.expression.Expression;
import org.hibernate.type.SqlTypes;
import org.hibernate.type.spi.TypeConfiguration;
/**
@ -49,6 +52,11 @@ public class OracleJsonObjectFunction extends JsonObjectFunction {
}
else {
value.accept( walker );
final JdbcMappingContainer expressionType = ( (Expression) value ).getExpressionType();
if ( expressionType != null && expressionType.getSingleJdbcMapping().getJdbcType().isJson()
&& !SqlTypes.isJsonType( expressionType.getSingleJdbcMapping().getJdbcType().getDdlTypeCode() ) ) {
sqlAppender.appendSql( " format json" );
}
}
}
}

View File

@ -0,0 +1,41 @@
/*
* 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_remove function.
*/
public class OracleJsonRemoveFunction extends AbstractJsonRemoveFunction {
public OracleJsonRemoveFunction(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 );
sqlAppender.appendSql( "json_transform(" );
json.accept( translator );
sqlAppender.appendSql( ",remove " );
jsonPath.accept( translator );
sqlAppender.appendSql( ')' );
}
}

View File

@ -0,0 +1,76 @@
/*
* 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 PostgreSQLJsonRemoveFunction extends AbstractJsonRemoveFunction {
public PostgreSQLJsonRemoveFunction(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 );
sqlAppender.appendSql( "jsonb_set_lax(" );
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[],null,true,'delete_key')" );
}
private boolean isJsonType(Expression expression) {
final JdbcMappingContainer expressionType = expression.getExpressionType();
return expressionType != null && expressionType.getSingleJdbcMapping().getJdbcType().isJson();
}
}

View File

@ -0,0 +1,41 @@
/*
* 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_remove function.
*/
public class SQLServerJsonRemoveFunction extends AbstractJsonRemoveFunction {
public SQLServerJsonRemoveFunction(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 );
sqlAppender.appendSql( "json_modify(" );
json.accept( translator );
sqlAppender.appendSql( ',' );
jsonPath.accept( translator );
sqlAppender.appendSql( ",null)" );
}
}

View File

@ -3943,6 +3943,22 @@ public interface HibernateCriteriaBuilder extends CriteriaBuilder {
@Incubating
JpaExpression<String> jsonSet(Expression<?> jsonDocument, Expression<String> jsonPath, Object value);
/**
* Removes a value by JSON path within a JSON document.
*
* @since 7.0
*/
@Incubating
JpaExpression<String> jsonRemove(Expression<?> jsonDocument, String jsonPath);
/**
* Removes a value by JSON path within a JSON document.
*
* @since 7.0
*/
@Incubating
JpaExpression<String> jsonRemove(Expression<?> jsonDocument, Expression<String> jsonPath);
@Override
JpaPredicate and(List<Predicate> restrictions);

View File

@ -3547,4 +3547,16 @@ public class HibernateCriteriaBuilderDelegate implements HibernateCriteriaBuilde
public JpaExpression<String> jsonSet(Expression<?> jsonDocument, Expression<String> jsonPath, Object value) {
return criteriaBuilder.jsonSet( jsonDocument, jsonPath, value );
}
@Override
@Incubating
public JpaExpression<String> jsonRemove(Expression<?> jsonDocument, String jsonPath) {
return criteriaBuilder.jsonRemove( jsonDocument, jsonPath );
}
@Override
@Incubating
public JpaExpression<String> jsonRemove(Expression<?> jsonDocument, Expression<String> jsonPath) {
return criteriaBuilder.jsonRemove( jsonDocument, jsonPath );
}
}

View File

@ -715,6 +715,12 @@ public interface NodeBuilder extends HibernateCriteriaBuilder, BindingContext {
@Override
SqmExpression<String> jsonSet(Expression<?> jsonDocument, String jsonPath, Expression<?> value);
@Override
SqmExpression<String> jsonRemove(Expression<?> jsonDocument, String jsonPath);
@Override
SqmExpression<String> jsonRemove(Expression<?> jsonDocument, Expression<String> jsonPath);
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Covariant overrides

View File

@ -5580,4 +5580,19 @@ public class SqmCriteriaNodeBuilder implements NodeBuilder, Serializable {
queryEngine
);
}
@Override
public SqmExpression<String> jsonRemove(Expression<?> jsonDocument, String jsonPath) {
return jsonRemove( jsonDocument, value( jsonPath ) );
}
@Override
public SqmExpression<String> jsonRemove(Expression<?> jsonDocument, Expression<String> jsonPath) {
//noinspection unchecked
return getFunctionDescriptor( "json_remove" ).generateSqmExpression(
(List<? extends SqmTypedNode<?>>) (List<?>) asList( jsonDocument, jsonPath ),
null,
queryEngine
);
}
}

View File

@ -0,0 +1,39 @@
/*
* 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.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.SupportsJsonRemove.class)
public class JsonRemoveTest {
@Test
public void testSimple(SessionFactoryScope scope) {
scope.inSession( em -> {
//tag::hql-json-remove-example[]
em.createQuery( "select json_remove('{\"a\":1,\"b\":2}', '$.a')" ).getResultList();
//end::hql-json-remove-example[]
} );
}
}

View File

@ -498,6 +498,52 @@ public class JsonFunctionTests {
);
}
@Test
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsJsonRemove.class)
public void testJsonRemove(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
String json = session.createQuery(
"select json_remove('{\"a\":123,\"b\":456}', '$.a')",
String.class
).getSingleResult();
Map<String, Object> object = parseObject( json );
assertEquals( 1, object.size() );
assertEquals( 456, object.get( "b" ) );
}
);
}
@Test
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsJsonRemove.class)
public void testJsonRemoveToEmpty(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
String json = session.createQuery(
"select json_remove('{\"a\":123}', '$.a')",
String.class
).getSingleResult();
Map<String, Object> object = parseObject( json );
assertEquals( 0, object.size() );
}
);
}
@Test
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsJsonRemove.class)
public void testJsonRemoveNonExisting(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
String json = session.createQuery(
"select json_remove('{}', '$.a')",
String.class
).getSingleResult();
Map<String, Object> object = parseObject( json );
assertEquals( 0, object.size() );
}
);
}
private static final ObjectMapper MAPPER = new ObjectMapper();
private static Map<String, Object> parseObject(String json) {

View File

@ -799,6 +799,12 @@ abstract public class DialectFeatureChecks {
}
}
public static class SupportsJsonRemove implements DialectFeatureCheck {
public boolean apply(Dialect dialect) {
return definesFunction( dialect, "json_remove" );
}
}
public static class IsJtds implements DialectFeatureCheck {
public boolean apply(Dialect dialect) {
return dialect instanceof SybaseDialect && ( (SybaseDialect) dialect ).getDriverKind() == SybaseDriverKind.JTDS;