HHH-18496 Hide JSON functions behind feature flag

This commit is contained in:
Christian Beikov 2024-09-11 20:19:17 +02:00
parent 59ae75bb52
commit f6ac53f83f
18 changed files with 107 additions and 1 deletions

View File

@ -1625,6 +1625,9 @@ include::{array-example-dir-hql}/ArrayToStringTest.java[tags=hql-array-to-string
The following functions deal with SQL JSON types, which are not supported on every database.
NOTE: The following functions are incubating/tech-preview and to use them in HQL,
it is necessary to enable the `hibernate.query.hql.json_functions_enabled` configuration setting.
[[hql-json-functions]]
|===
| Function | Purpose

View File

@ -130,6 +130,7 @@ import static org.hibernate.cfg.AvailableSettings.USE_SUBSELECT_FETCH;
import static org.hibernate.cfg.CacheSettings.QUERY_CACHE_LAYOUT;
import static org.hibernate.cfg.PersistenceSettings.UNOWNED_ASSOCIATION_TRANSIENT_CHECK;
import static org.hibernate.cfg.QuerySettings.DEFAULT_NULL_ORDERING;
import static org.hibernate.cfg.QuerySettings.JSON_FUNCTIONS_ENABLED;
import static org.hibernate.cfg.QuerySettings.PORTABLE_INTEGER_DIVISION;
import static org.hibernate.engine.config.spi.StandardConverters.BOOLEAN;
import static org.hibernate.internal.CoreLogging.messageLogger;
@ -276,6 +277,7 @@ public class SessionFactoryOptionsBuilder implements SessionFactoryOptions {
private final boolean inClauseParameterPaddingEnabled;
private final boolean portableIntegerDivisionEnabled;
private final boolean jsonFunctionsEnabled;
private final int queryStatisticsMaxSize;
@ -616,6 +618,10 @@ public class SessionFactoryOptionsBuilder implements SessionFactoryOptions {
PORTABLE_INTEGER_DIVISION,
configurationSettings
);
this.jsonFunctionsEnabled = getBoolean(
JSON_FUNCTIONS_ENABLED,
configurationSettings
);
this.queryStatisticsMaxSize = getInt(
QUERY_STATISTICS_MAX_SIZE,
@ -1246,6 +1252,11 @@ public class SessionFactoryOptionsBuilder implements SessionFactoryOptions {
return this.inClauseParameterPaddingEnabled;
}
@Override
public boolean isJsonFunctionsEnabled() {
return jsonFunctionsEnabled;
}
@Override
public boolean isPortableIntegerDivisionEnabled() {
return portableIntegerDivisionEnabled;

View File

@ -421,6 +421,11 @@ public class AbstractDelegatingSessionFactoryOptions implements SessionFactoryOp
return delegate.inClauseParameterPaddingEnabled();
}
@Override
public boolean isJsonFunctionsEnabled() {
return delegate.isJsonFunctionsEnabled();
}
@Override
public boolean isPortableIntegerDivisionEnabled() {
return delegate.isPortableIntegerDivisionEnabled();

View File

@ -266,6 +266,14 @@ public interface SessionFactoryOptions extends QueryEngineOptions {
return false;
}
/**
* @see org.hibernate.cfg.AvailableSettings#JSON_FUNCTIONS_ENABLED
*/
@Override
default boolean isJsonFunctionsEnabled() {
return false;
}
/**
* @see org.hibernate.cfg.AvailableSettings#PORTABLE_INTEGER_DIVISION
*/

View File

@ -4,6 +4,7 @@
*/
package org.hibernate.cfg;
import org.hibernate.Incubating;
import org.hibernate.boot.spi.SessionFactoryOptions;
import org.hibernate.query.spi.QueryPlan;
@ -15,6 +16,14 @@ import jakarta.persistence.criteria.CriteriaUpdate;
* @author Steve Ebersole
*/
public interface QuerySettings {
/**
* Boolean setting to control if the use of tech preview JSON functions in HQL is enabled.
* By default, this is {@code false} i.e. disabled since the functions are still incubating.
*
* @since 7.0
*/
@Incubating
String JSON_FUNCTIONS_ENABLED = "hibernate.query.hql.json_functions_enabled";
/**
* Specifies that division of two integers should produce an integer on all
* databases. By default, integer division in HQL can produce a non-integer

View File

@ -32,6 +32,7 @@ import java.util.Map;
import java.util.Set;
import org.hibernate.boot.registry.classloading.spi.ClassLoadingException;
import org.hibernate.cfg.QuerySettings;
import org.hibernate.dialect.function.SqlColumn;
import org.hibernate.grammars.hql.HqlLexer;
import org.hibernate.grammars.hql.HqlParser;
@ -2699,6 +2700,7 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
@Override
public SqmExpression<?> visitJsonValueFunction(HqlParser.JsonValueFunctionContext ctx) {
checkJsonFunctionsEnabled( ctx );
final SqmExpression<?> jsonDocument = (SqmExpression<?>) ctx.expression( 0 ).accept( this );
final SqmExpression<?> jsonPath = (SqmExpression<?>) ctx.expression( 1 ).accept( this );
final HqlParser.JsonValueReturningClauseContext returningClause = ctx.jsonValueReturningClause();
@ -2749,6 +2751,7 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
@Override
public SqmExpression<?> visitJsonQueryFunction(HqlParser.JsonQueryFunctionContext ctx) {
checkJsonFunctionsEnabled( ctx );
final SqmExpression<?> jsonDocument = (SqmExpression<?>) ctx.expression( 0 ).accept( this );
final SqmExpression<?> jsonPath = (SqmExpression<?>) ctx.expression( 1 ).accept( this );
final SqmJsonQueryExpression jsonQuery = (SqmJsonQueryExpression) getFunctionDescriptor( "json_query" ).<String>generateSqmExpression(
@ -2822,6 +2825,7 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
@Override
public SqmExpression<?> visitJsonExistsFunction(HqlParser.JsonExistsFunctionContext ctx) {
checkJsonFunctionsEnabled( ctx );
final SqmExpression<?> jsonDocument = (SqmExpression<?>) ctx.expression( 0 ).accept( this );
final SqmExpression<?> jsonPath = (SqmExpression<?>) ctx.expression( 1 ).accept( this );
@ -2855,6 +2859,7 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
@Override
public SqmExpression<?> visitJsonArrayFunction(HqlParser.JsonArrayFunctionContext ctx) {
checkJsonFunctionsEnabled( ctx );
final HqlParser.JsonNullClauseContext subCtx = ctx.jsonNullClause();
final List<HqlParser.ExpressionOrPredicateContext> argumentContexts = ctx.expressionOrPredicate();
int count = argumentContexts.size();
@ -2879,6 +2884,7 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
@Override
public SqmExpression<?> visitJsonObjectFunction(HqlParser.JsonObjectFunctionContext ctx) {
checkJsonFunctionsEnabled( ctx );
final HqlParser.JsonObjectFunctionEntriesContext entries = ctx.jsonObjectFunctionEntries();
final List<SqmTypedNode<?>> arguments;
if ( entries == null ) {
@ -2910,6 +2916,7 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
@Override
public Object visitJsonArrayAggFunction(HqlParser.JsonArrayAggFunctionContext ctx) {
checkJsonFunctionsEnabled( ctx );
final HqlParser.JsonNullClauseContext jsonNullClauseContext = ctx.jsonNullClause();
final ArrayList<SqmTypedNode<?>> arguments = new ArrayList<>( jsonNullClauseContext == null ? 1 : 2 );
arguments.add( (SqmTypedNode<?>) ctx.expressionOrPredicate().accept( this ) );
@ -2934,6 +2941,7 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
@Override
public Object visitJsonObjectAggFunction(HqlParser.JsonObjectAggFunctionContext ctx) {
checkJsonFunctionsEnabled( ctx );
final HqlParser.JsonNullClauseContext jsonNullClauseContext = ctx.jsonNullClause();
final HqlParser.JsonUniqueKeysClauseContext jsonUniqueKeysClauseContext = ctx.jsonUniqueKeysClause();
final ArrayList<SqmTypedNode<?>> arguments = new ArrayList<>( 4 );
@ -2964,6 +2972,16 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
);
}
private void checkJsonFunctionsEnabled(ParserRuleContext ctx) {
if ( !creationOptions.isJsonFunctionsEnabled() ) {
throw new SemanticException(
"Can't use function '" + ctx.children.get( 0 ).getText() +
"', because tech preview JSON functions are not enabled. To enable, set the '" + QuerySettings.JSON_FUNCTIONS_ENABLED + "' setting to 'true'.",
query
);
}
}
@Override
public SqmPredicate visitIncludesPredicate(HqlParser.IncludesPredicateContext ctx) {
final boolean negated = ctx.NOT() != null;

View File

@ -24,6 +24,13 @@ public interface SqmCreationOptions {
return false;
}
/**
* @see org.hibernate.cfg.AvailableSettings#JSON_FUNCTIONS_ENABLED
*/
default boolean isJsonFunctionsEnabled() {
return false;
}
/**
* @see org.hibernate.cfg.AvailableSettings#PORTABLE_INTEGER_DIVISION
*/

View File

@ -76,6 +76,11 @@ public interface QueryEngineOptions {
return ImmutableEntityUpdateQueryHandlingMode.WARNING;
}
/**
* @see org.hibernate.cfg.AvailableSettings#JSON_FUNCTIONS_ENABLED
*/
boolean isJsonFunctionsEnabled();
/**
* @see org.hibernate.cfg.AvailableSettings#PORTABLE_INTEGER_DIVISION
*/

View File

@ -22,6 +22,11 @@ public class SqmCreationOptionsStandard implements SqmCreationOptions {
return queryEngineOptions.getJpaCompliance().isJpaQueryComplianceEnabled();
}
@Override
public boolean isJsonFunctionsEnabled() {
return queryEngineOptions.isJsonFunctionsEnabled();
}
@Override
public boolean isPortableIntegerDivisionEnabled() {
return queryEngineOptions.isPortableIntegerDivisionEnabled();

View File

@ -6,12 +6,16 @@
*/
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;
/**
@ -19,6 +23,7 @@ import org.junit.jupiter.api.Test;
*/
@DomainModel(standardModels = StandardDomainModel.GAMBIT)
@SessionFactory
@ServiceRegistry(settings = @Setting(name = QuerySettings.JSON_FUNCTIONS_ENABLED, value = "true"))
@RequiresDialectFeature( feature = DialectFeatureChecks.SupportsJsonArrayAgg.class)
public class JsonArrayAggregateTest {

View File

@ -6,11 +6,15 @@
*/
package org.hibernate.orm.test.function.json;
import org.hibernate.cfg.QuerySettings;
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;
/**
@ -18,6 +22,7 @@ import org.junit.jupiter.api.Test;
*/
@DomainModel
@SessionFactory
@ServiceRegistry(settings = @Setting(name = QuerySettings.JSON_FUNCTIONS_ENABLED, value = "true"))
@RequiresDialectFeature( feature = DialectFeatureChecks.SupportsJsonArray.class)
public class JsonArrayTest {

View File

@ -11,6 +11,7 @@ import java.util.List;
import org.hibernate.HibernateException;
import org.hibernate.JDBCException;
import org.hibernate.cfg.QuerySettings;
import org.hibernate.dialect.MariaDBDialect;
import org.hibernate.dialect.OracleDialect;
import org.hibernate.sql.exec.ExecutionException;
@ -18,8 +19,10 @@ import org.hibernate.sql.exec.ExecutionException;
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.AfterEach;
import org.junit.jupiter.api.BeforeEach;
@ -33,6 +36,7 @@ import static org.junit.jupiter.api.Assertions.fail;
*/
@DomainModel(annotatedClasses = EntityWithJson.class)
@SessionFactory
@ServiceRegistry(settings = @Setting(name = QuerySettings.JSON_FUNCTIONS_ENABLED, value = "true"))
@RequiresDialectFeature( feature = DialectFeatureChecks.SupportsJsonExists.class)
public class JsonExistsTest {

View File

@ -6,6 +6,7 @@
*/
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;
@ -17,8 +18,10 @@ 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;
@ -27,6 +30,7 @@ import org.junit.jupiter.api.Test;
*/
@DomainModel(standardModels = StandardDomainModel.GAMBIT)
@SessionFactory
@ServiceRegistry(settings = @Setting(name = QuerySettings.JSON_FUNCTIONS_ENABLED, value = "true"))
@RequiresDialectFeature( feature = DialectFeatureChecks.SupportsJsonObjectAgg.class)
public class JsonObjectAggregateTest {

View File

@ -6,11 +6,15 @@
*/
package org.hibernate.orm.test.function.json;
import org.hibernate.cfg.QuerySettings;
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;
/**
@ -18,6 +22,7 @@ import org.junit.jupiter.api.Test;
*/
@DomainModel
@SessionFactory
@ServiceRegistry(settings = @Setting(name = QuerySettings.JSON_FUNCTIONS_ENABLED, value = "true"))
@RequiresDialectFeature( feature = DialectFeatureChecks.SupportsJsonObject.class)
public class JsonObjectTest {

View File

@ -11,14 +11,17 @@ import java.util.List;
import org.hibernate.HibernateException;
import org.hibernate.JDBCException;
import org.hibernate.cfg.QuerySettings;
import org.hibernate.dialect.MariaDBDialect;
import org.hibernate.sql.exec.ExecutionException;
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.AfterEach;
import org.junit.jupiter.api.BeforeEach;
@ -34,6 +37,7 @@ import static org.junit.jupiter.api.Assertions.fail;
*/
@DomainModel(annotatedClasses = EntityWithJson.class)
@SessionFactory
@ServiceRegistry(settings = @Setting(name = QuerySettings.JSON_FUNCTIONS_ENABLED, value = "true"))
@RequiresDialectFeature( feature = DialectFeatureChecks.SupportsJsonQuery.class)
public class JsonQueryTest {

View File

@ -11,14 +11,17 @@ import java.util.List;
import org.hibernate.HibernateException;
import org.hibernate.JDBCException;
import org.hibernate.cfg.QuerySettings;
import org.hibernate.dialect.MariaDBDialect;
import org.hibernate.sql.exec.ExecutionException;
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.AfterEach;
import org.junit.jupiter.api.BeforeEach;
@ -34,6 +37,7 @@ import static org.junit.jupiter.api.Assertions.fail;
*/
@DomainModel(annotatedClasses = EntityWithJson.class)
@SessionFactory
@ServiceRegistry(settings = @Setting(name = QuerySettings.JSON_FUNCTIONS_ENABLED, value = "true"))
@RequiresDialectFeature( feature = DialectFeatureChecks.SupportsJsonValue.class)
public class JsonValueTest {

View File

@ -28,7 +28,7 @@ import static org.junit.jupiter.api.Assertions.assertNull;
public class SerializableTypeTest {
@Test
@SkipForDialect(dialectClass = SybaseASEDialect.class, majorVersion = 15, matchSubTypes = true, reason = "HHH-6425")
@SkipForDialect(dialectClass = SybaseASEDialect.class, reason = "HHH-6425")
public void testNewSerializableType(SessionFactoryScope scope) {
final String initialPayloadText = "Initial payload";
final String changedPayloadText = "Changed payload";

View File

@ -17,6 +17,7 @@ import java.util.UUID;
import org.hibernate.HibernateException;
import org.hibernate.JDBCException;
import org.hibernate.annotations.JdbcTypeCode;
import org.hibernate.cfg.QuerySettings;
import org.hibernate.dialect.CockroachDialect;
import org.hibernate.dialect.DB2Dialect;
import org.hibernate.dialect.HANADialect;
@ -33,8 +34,10 @@ import org.hibernate.testing.orm.junit.DialectFeatureChecks;
import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.Jira;
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.AfterEach;
import org.junit.jupiter.api.BeforeEach;
@ -67,6 +70,7 @@ import static org.junit.jupiter.api.Assertions.fail;
JsonFunctionTests.JsonHolder.class,
EntityOfBasics.class
})
@ServiceRegistry(settings = @Setting(name = QuerySettings.JSON_FUNCTIONS_ENABLED, value = "true"))
@SessionFactory
@Jira("https://hibernate.atlassian.net/browse/HHH-18496")
public class JsonFunctionTests {