HHH-18496 Add json_value function

This commit is contained in:
Christian Beikov 2024-08-17 03:11:51 +02:00
parent ff57a6ced0
commit d5a3f041b3
51 changed files with 2858 additions and 0 deletions

View File

@ -6,6 +6,7 @@
:core-project-dir: {root-project-dir}/hibernate-core
:example-dir-hql: {core-project-dir}/src/test/java/org/hibernate/orm/test/hql
:array-example-dir-hql: {core-project-dir}/src/test/java/org/hibernate/orm/test/function/array
:json-example-dir-hql: {core-project-dir}/src/test/java/org/hibernate/orm/test/function/json
:extrasdir: extras
This chapter describes Hibernate Query Language (HQL) and Jakarta Persistence Query Language (JPQL).
@ -1619,6 +1620,85 @@ include::{array-example-dir-hql}/ArrayToStringTest.java[tags=hql-array-to-string
----
====
[[hql-functions-json]]
==== Functions for dealing with JSON
The following functions deal with SQL JSON types, which are not supported on every database.
[[hql-json-functions]]
|===
| Function | Purpose
| `json_value()` | Extracts a value from a JSON document by JSON path
|===
[[hql-json-value-function]]
===== `json_value()`
Extracts a value by https://www.ietf.org/archive/id/draft-goessner-dispatch-jsonpath-00.html[JSON path] from a JSON document.
[[hql-like-json-value-bnf]]
[source, antlrv4, indent=0]
----
include::{extrasdir}/json_value_bnf.txt[]
----
The first argument is an expression to a JSON document. The second argument is a JSON path as String expression.
NOTE: It is recommended to only us the dot notation for JSON paths, since most databases support only that.
[[hql-json-value-example]]
====
[source, java, indent=0]
----
include::{json-example-dir-hql}/JsonValueTest.java[tags=hql-json-value-example]
----
====
The `returning` clause allows to specify the <<hql-function-cast,cast target>> i.e. the type of value to extract.
[[hql-json-value-returning-example]]
====
[source, java, indent=0]
----
include::{json-example-dir-hql}/JsonValueTest.java[tags=hql-json-value-returning-example]
----
====
The `on empty` clause defines the behavior when the JSON path does not match the JSON document.
By default, `null` is returned on empty.
[[hql-json-value-on-error-example]]
====
[source, java, indent=0]
----
include::{json-example-dir-hql}/JsonValueTest.java[tags=hql-json-value-on-error-example]
----
====
The `on error` clause defines the behavior when an error occurs while resolving the value for the JSON path.
Conditions that classify as errors are database dependent, but usual errors which can be handled with this clause are:
* First argument is not a valid JSON document
* Second argument is not a valid JSON path
* JSON path does not resolve to a scalar value
The default behavior of `on error` is database specific, but usually, `null` is returned on an error.
It is recommended to specify this clause when the exact error behavior is important.
[[hql-json-value-on-empty-example]]
====
[source, java, indent=0]
----
include::{json-example-dir-hql}/JsonValueTest.java[tags=hql-json-value-on-empty-example]
----
====
To actually receive an error `on empty`, it is necessary to also specify `error on error`.
Depending on the database, an error might still be thrown even without that, but that is not portable.
NOTE: The H2 emulation only supports absolute JSON paths using the dot notation.
[[hql-user-defined-functions]]
==== Native and user-defined functions

View File

@ -0,0 +1,7 @@
"json_value(" expression, expression ("returning" castTarget)? onErrorClause? onEmptyClause? ")"
onErrorClause
: ( "error" | "null" | ( "default" expression ) ) "on error";
onEmptyClause
: ( "error" | "null" | ( "default" expression ) ) "on empty";

View File

@ -492,6 +492,8 @@ public class CockroachLegacyDialect extends Dialect {
functionFactory.arrayFill_cockroachdb();
functionFactory.arrayToString_postgresql();
functionFactory.jsonValue_cockroachdb();
// Postgres uses # instead of ^ for XOR
functionContributions.getFunctionRegistry().patternDescriptorBuilder( "bitxor", "(?1#?2)" )
.setExactArgumentCount( 2 )

View File

@ -429,6 +429,10 @@ public class DB2LegacyDialect extends Dialect {
functionFactory.windowFunctions();
if ( getDB2Version().isSameOrAfter( 9, 5 ) ) {
functionFactory.listagg( null );
if ( getDB2Version().isSameOrAfter( 11 ) ) {
functionFactory.jsonValue();
}
}
}

View File

@ -398,6 +398,10 @@ public class H2LegacyDialect extends Dialect {
functionFactory.arrayTrim_trim_array();
functionFactory.arrayFill_h2();
functionFactory.arrayToString_h2( getMaximumArraySize() );
if ( getVersion().isSameOrAfter( 2, 2, 220 ) ) {
functionFactory.jsonValue_h2();
}
}
else {
// Use group_concat until 2.x as listagg was buggy

View File

@ -89,6 +89,7 @@ public class MariaDBLegacyDialect extends MySQLLegacyDialect {
.getBasicTypeRegistry()
.resolve( StandardBasicTypes.BOOLEAN )
);
commonFunctionFactory.jsonValue_mariadb();
if ( getVersion().isSameOrAfter( 10, 3, 3 ) ) {
commonFunctionFactory.inverseDistributionOrderedSetAggregates_windowEmulation();
functionContributions.getFunctionRegistry().patternDescriptorBuilder( "median", "median(?1) over ()" )

View File

@ -651,6 +651,10 @@ public class MySQLLegacyDialect extends Dialect {
functionRegistry.registerAlternateKey( "char", "chr" );
functionFactory.listagg_groupConcat();
if ( getMySQLVersion().isSameOrAfter( 5, 7 ) ) {
functionFactory.jsonValue_mysql();
}
}
@Override

View File

@ -318,6 +318,10 @@ public class OracleLegacyDialect extends Dialect {
functionFactory.arrayTrim_oracle();
functionFactory.arrayFill_oracle();
functionFactory.arrayToString_oracle();
if ( getVersion().isSameOrAfter( 12 ) ) {
functionFactory.jsonValue_literal_path();
}
}
@Override

View File

@ -629,6 +629,13 @@ public class PostgreSQLLegacyDialect extends Dialect {
functionFactory.arrayFill_postgresql();
functionFactory.arrayToString_postgresql();
if ( getVersion().isSameOrAfter( 17 ) ) {
functionFactory.jsonValue();
}
else {
functionFactory.jsonValue_postgresql();
}
if ( getVersion().isSameOrAfter( 9, 4 ) ) {
functionFactory.makeDateTimeTimestamp();
// Note that PostgreSQL doesn't support the OVER clause for ordered set-aggregate functions

View File

@ -400,6 +400,9 @@ public class SQLServerLegacyDialect extends AbstractTransactSQLDialect {
functionFactory.windowFunctions();
functionFactory.inverseDistributionOrderedSetAggregates_windowEmulation();
functionFactory.hypotheticalOrderedSetAggregates_windowEmulation();
if ( getVersion().isSameOrAfter( 13 ) ) {
functionFactory.jsonValue_sqlserver();
}
if ( getVersion().isSameOrAfter( 14 ) ) {
functionFactory.listagg_stringAggWithinGroup( "varchar(max)" );
}

View File

@ -221,6 +221,7 @@ INTERSECTS : [iI] [nN] [tT] [eE] [rR] [sS] [eE] [cC] [tT] [sS];
INTO : [iI] [nN] [tT] [oO];
IS : [iI] [sS];
JOIN : [jJ] [oO] [iI] [nN];
JSON_VALUE : [jJ] [sS] [oO] [nN] '_' [vV] [aA] [lL] [uU] [eE];
KEY : [kK] [eE] [yY];
KEYS : [kK] [eE] [yY] [sS];
LAST : [lL] [aA] [sS] [tT];
@ -277,6 +278,7 @@ PRECEDING : [pP] [rR] [eE] [cC] [eE] [dD] [iI] [nN] [gG];
QUARTER : [qQ] [uU] [aA] [rR] [tT] [eE] [rR];
RANGE : [rR] [aA] [nN] [gG] [eE];
RESPECT : [rR] [eE] [sS] [pP] [eE] [cC] [tT];
RETURNING : [rR] [eE] [tT] [uU] [rR] [nN] [iI] [nN] [gG];
RIGHT : [rR] [iI] [gG] [hH] [tT];
ROLLUP : [rR] [oO] [lL] [lL] [uU] [pP];
ROW : [rR] [oO] [wW];

View File

@ -1109,6 +1109,7 @@ function
| collectionFunctionMisuse
| jpaNonstandardFunction
| columnFunction
| jsonValueFunction
| genericFunction
;
@ -1620,6 +1621,20 @@ rollup
: ROLLUP LEFT_PAREN expressionOrPredicate (COMMA expressionOrPredicate)* RIGHT_PAREN
;
/**
* The 'json_value()' function
*/
jsonValueFunction
: JSON_VALUE LEFT_PAREN expression COMMA expression jsonValueReturningClause? jsonValueOnErrorOrEmptyClause? jsonValueOnErrorOrEmptyClause? RIGHT_PAREN
;
jsonValueReturningClause
: RETURNING castTarget
;
jsonValueOnErrorOrEmptyClause
: ( ERROR | NULL | ( DEFAULT expression ) ) ON (ERROR|EMPTY);
/**
* Support for "soft" keywords which may be used as identifiers
*
@ -1714,6 +1729,7 @@ rollup
| INTO
| IS
| JOIN
| JSON_VALUE
| KEY
| KEYS
| LAST
@ -1771,6 +1787,7 @@ rollup
| QUARTER
| RANGE
| RESPECT
| RETURNING
// | RIGHT
| ROLLUP
| ROW

View File

@ -463,6 +463,8 @@ public class CockroachDialect extends Dialect {
functionFactory.arrayFill_cockroachdb();
functionFactory.arrayToString_postgresql();
functionFactory.jsonValue_cockroachdb();
// Postgres uses # instead of ^ for XOR
functionContributions.getFunctionRegistry().patternDescriptorBuilder( "bitxor", "(?1#?2)" )
.setExactArgumentCount( 2 )

View File

@ -415,6 +415,10 @@ public class DB2Dialect extends Dialect {
functionFactory.windowFunctions();
functionFactory.listagg( null );
if ( getDB2Version().isSameOrAfter( 11 ) ) {
functionFactory.jsonValue();
}
}
@Override

View File

@ -339,6 +339,10 @@ public class H2Dialect extends Dialect {
functionFactory.arrayTrim_trim_array();
functionFactory.arrayFill_h2();
functionFactory.arrayToString_h2( getMaximumArraySize() );
if ( getVersion().isSameOrAfter( 2, 2, 220 ) ) {
functionFactory.jsonValue_h2();
}
}
/**

View File

@ -488,6 +488,11 @@ public class HANADialect extends Dialect {
ANY, ANY, ANY,
typeConfiguration
);
if ( getVersion().isSameOrAfter(2, 0, 20) ) {
// Introduced in 2.0 SPS 02
functionFactory.jsonValue();
}
}
@Override

View File

@ -92,6 +92,7 @@ public class MariaDBDialect extends MySQLDialect {
.getBasicTypeRegistry()
.resolve( StandardBasicTypes.BOOLEAN )
);
commonFunctionFactory.jsonValue_mariadb();
commonFunctionFactory.inverseDistributionOrderedSetAggregates_windowEmulation();
functionContributions.getFunctionRegistry().patternDescriptorBuilder( "median", "median(?1) over ()" )
.setInvariantType( functionContributions.getTypeConfiguration().getBasicTypeRegistry().resolve( StandardBasicTypes.DOUBLE ) )

View File

@ -632,6 +632,8 @@ public class MySQLDialect extends Dialect {
}
functionFactory.listagg_groupConcat();
functionFactory.jsonValue_mysql();
}
@Override

View File

@ -386,6 +386,8 @@ public class OracleDialect extends Dialect {
functionFactory.arrayTrim_oracle();
functionFactory.arrayFill_oracle();
functionFactory.arrayToString_oracle();
functionFactory.jsonValue_literal_path();
}
@Override

View File

@ -591,6 +591,13 @@ public class PostgreSQLDialect extends Dialect {
functionFactory.arrayFill_postgresql();
functionFactory.arrayToString_postgresql();
if ( getVersion().isSameOrAfter( 17 ) ) {
functionFactory.jsonValue();
}
else {
functionFactory.jsonValue_postgresql();
}
functionFactory.makeDateTimeTimestamp();
// Note that PostgreSQL doesn't support the OVER clause for ordered set-aggregate functions
functionFactory.inverseDistributionOrderedSetAggregates();

View File

@ -418,6 +418,9 @@ public class SQLServerDialect extends AbstractTransactSQLDialect {
functionFactory.windowFunctions();
functionFactory.inverseDistributionOrderedSetAggregates_windowEmulation();
functionFactory.hypotheticalOrderedSetAggregates_windowEmulation();
if ( getVersion().isSameOrAfter( 13 ) ) {
functionFactory.jsonValue_sqlserver();
}
if ( getVersion().isSameOrAfter( 14 ) ) {
functionFactory.listagg_stringAggWithinGroup( "varchar(max)" );
}

View File

@ -76,6 +76,13 @@ import org.hibernate.dialect.function.array.OracleArrayConstructorFunction;
import org.hibernate.dialect.function.array.OracleArrayContainsFunction;
import org.hibernate.dialect.function.array.PostgreSQLArrayPositionsFunction;
import org.hibernate.dialect.function.array.PostgreSQLArrayTrimEmulation;
import org.hibernate.dialect.function.json.CockroachDBJsonValueFunction;
import org.hibernate.dialect.function.json.H2JsonValueFunction;
import org.hibernate.dialect.function.json.JsonValueFunction;
import org.hibernate.dialect.function.json.MariaDBJsonValueFunction;
import org.hibernate.dialect.function.json.MySQLJsonValueFunction;
import org.hibernate.dialect.function.json.PostgreSQLJsonValueFunction;
import org.hibernate.dialect.function.json.SQLServerJsonValueFunction;
import org.hibernate.query.sqm.function.SqmFunctionRegistry;
import org.hibernate.query.sqm.produce.function.ArgumentTypesValidator;
import org.hibernate.query.sqm.produce.function.StandardArgumentsValidators;
@ -3320,4 +3327,60 @@ public class CommonFunctionFactory {
public void arrayToString_oracle() {
functionRegistry.register( "array_to_string", new OracleArrayToStringFunction( typeConfiguration ) );
}
/**
* json_value() function
*/
public void jsonValue() {
functionRegistry.register( "json_value", new JsonValueFunction( typeConfiguration, true ) );
}
/**
* json_value() function that supports only literal json paths
*/
public void jsonValue_literal_path() {
functionRegistry.register( "json_value", new JsonValueFunction( typeConfiguration, false ) );
}
/**
* PostgreSQL json_value() function
*/
public void jsonValue_postgresql() {
functionRegistry.register( "json_value", new PostgreSQLJsonValueFunction( typeConfiguration ) );
}
/**
* CockroachDB json_value() function
*/
public void jsonValue_cockroachdb() {
functionRegistry.register( "json_value", new CockroachDBJsonValueFunction( typeConfiguration ) );
}
/**
* MySQL json_value() function
*/
public void jsonValue_mysql() {
functionRegistry.register( "json_value", new MySQLJsonValueFunction( typeConfiguration ) );
}
/**
* MariaDB json_value() function
*/
public void jsonValue_mariadb() {
functionRegistry.register( "json_value", new MariaDBJsonValueFunction( typeConfiguration ) );
}
/**
* SQL Server json_value() function
*/
public void jsonValue_sqlserver() {
functionRegistry.register( "json_value", new SQLServerJsonValueFunction( typeConfiguration ) );
}
/**
* H2 json_value() function
*/
public void jsonValue_h2() {
functionRegistry.register( "json_value", new H2JsonValueFunction( typeConfiguration ) );
}
}

View File

@ -0,0 +1,79 @@
/*
* 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 java.util.function.Supplier;
import org.hibernate.metamodel.mapping.BasicValuedMapping;
import org.hibernate.query.ReturnableType;
import org.hibernate.query.sqm.produce.function.FunctionReturnTypeResolver;
import org.hibernate.query.sqm.sql.SqmToSqlAstConverter;
import org.hibernate.query.sqm.tree.SqmTypedNode;
import org.hibernate.query.sqm.tree.expression.SqmCastTarget;
import org.hibernate.sql.ast.tree.SqlAstNode;
import org.hibernate.sql.ast.tree.expression.CastTarget;
import org.hibernate.type.BasicType;
import org.hibernate.type.spi.TypeConfiguration;
import org.checkerframework.checker.nullness.qual.Nullable;
import static org.hibernate.query.sqm.produce.function.StandardFunctionReturnTypeResolvers.extractArgumentType;
import static org.hibernate.query.sqm.produce.function.StandardFunctionReturnTypeResolvers.extractArgumentValuedMapping;
import static org.hibernate.query.sqm.produce.function.StandardFunctionReturnTypeResolvers.isAssignableTo;
import static org.hibernate.query.sqm.produce.function.StandardFunctionReturnTypeResolvers.useImpliedTypeIfPossible;
public class CastTargetReturnTypeResolver implements FunctionReturnTypeResolver {
private final BasicType<?> defaultType;
public CastTargetReturnTypeResolver(TypeConfiguration typeConfiguration) {
this.defaultType = typeConfiguration.getBasicTypeForJavaType( String.class );
}
@Override
public ReturnableType<?> resolveFunctionReturnType(
ReturnableType<?> impliedType,
@Nullable SqmToSqlAstConverter converter,
List<? extends SqmTypedNode<?>> arguments,
TypeConfiguration typeConfiguration) {
if ( arguments.size() > 2 ) {
int castTargetIndex = -1;
for ( int i = 2; i < arguments.size(); i++ ) {
if (arguments.get( i ) instanceof SqmCastTarget<?> ) {
castTargetIndex = i;
break;
}
}
if ( castTargetIndex != -1 ) {
ReturnableType<?> argType = extractArgumentType( arguments, castTargetIndex );
return isAssignableTo( argType, impliedType ) ? impliedType : argType;
}
}
return defaultType;
}
@Override
public BasicValuedMapping resolveFunctionReturnType(
Supplier<BasicValuedMapping> impliedTypeAccess,
List<? extends SqlAstNode> arguments) {
if ( arguments.size() > 2 ) {
int castTargetIndex = -1;
for ( int i = 2; i < arguments.size(); i++ ) {
if (arguments.get( i ) instanceof CastTarget ) {
castTargetIndex = i;
break;
}
}
if ( castTargetIndex != -1 ) {
final BasicValuedMapping specifiedArgType = extractArgumentValuedMapping( arguments, castTargetIndex );
return useImpliedTypeIfPossible( specifiedArgType, impliedTypeAccess.get() );
}
}
return defaultType;
}
}

View File

@ -0,0 +1,93 @@
/*
* 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.dialect.Dialect;
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.JdbcParameter;
import org.hibernate.sql.ast.tree.expression.JsonValueEmptyBehavior;
import org.hibernate.sql.ast.tree.expression.JsonValueErrorBehavior;
import org.hibernate.sql.ast.tree.expression.Literal;
import org.hibernate.type.spi.TypeConfiguration;
/**
* CockroachDB json_value function.
*/
public class CockroachDBJsonValueFunction extends JsonValueFunction {
public CockroachDBJsonValueFunction(TypeConfiguration typeConfiguration) {
super( typeConfiguration, true );
}
@Override
protected void render(
SqlAppender sqlAppender,
JsonValueArguments arguments,
ReturnableType<?> returnType,
SqlAstTranslator<?> walker) {
// jsonb_path_query_first errors by default
if ( arguments.errorBehavior() != null && arguments.errorBehavior() != JsonValueErrorBehavior.ERROR ) {
throw new QueryException( "Can't emulate on error clause on CockroachDB" );
}
if ( arguments.emptyBehavior() != null && arguments.emptyBehavior() != JsonValueEmptyBehavior.NULL ) {
throw new QueryException( "Can't emulate on empty clause on CockroachDB" );
}
final String jsonPath;
try {
jsonPath = walker.getLiteralValue( arguments.jsonPath() );
}
catch (Exception ex) {
throw new QueryException( "CockroachDB json_value only support literal json paths, but got " + arguments.jsonPath() );
}
final List<JsonPathHelper.JsonPathElement> jsonPathElements = JsonPathHelper.parseJsonPathElements( jsonPath );
if ( arguments.returningType() != null ) {
sqlAppender.appendSql( "cast(" );
}
final boolean needsCast = !arguments.isJsonType() && arguments.jsonDocument() instanceof JdbcParameter;
if ( needsCast ) {
sqlAppender.appendSql( "cast(" );
}
else {
sqlAppender.appendSql( '(' );
}
arguments.jsonDocument().accept( walker );
if ( needsCast ) {
sqlAppender.appendSql( " as jsonb)" );
}
else {
sqlAppender.appendSql( ')' );
}
sqlAppender.appendSql( "#>>array" );
char separator = '[';
final Dialect dialect = walker.getSessionFactory().getJdbcServices().getDialect();
for ( JsonPathHelper.JsonPathElement jsonPathElement : jsonPathElements ) {
sqlAppender.appendSql( separator );
if ( jsonPathElement instanceof JsonPathHelper.JsonAttribute attribute ) {
dialect.appendLiteral( sqlAppender, attribute.attribute() );
}
else {
sqlAppender.appendSql( '\'' );
sqlAppender.appendSql( ( (JsonPathHelper.JsonIndexAccess) jsonPathElement ).index() );
sqlAppender.appendSql( '\'' );
}
separator = ',';
}
sqlAppender.appendSql( ']' );
if ( arguments.returningType() != null ) {
sqlAppender.appendSql( " as " );
arguments.returningType().accept( walker );
sqlAppender.appendSql( ')' );
}
}
}

View File

@ -0,0 +1,118 @@
/*
* 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.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.JsonValueEmptyBehavior;
import org.hibernate.sql.ast.tree.expression.JsonValueErrorBehavior;
import org.hibernate.type.spi.TypeConfiguration;
/**
* H2 json_value function.
*/
public class H2JsonValueFunction extends JsonValueFunction {
public H2JsonValueFunction(TypeConfiguration typeConfiguration) {
super( typeConfiguration, false );
}
@Override
protected void render(
SqlAppender sqlAppender,
JsonValueArguments arguments,
ReturnableType<?> returnType,
SqlAstTranslator<?> walker) {
// Json dereference errors by default if the JSON is invalid
if ( arguments.errorBehavior() != null && arguments.errorBehavior() != JsonValueErrorBehavior.ERROR ) {
throw new QueryException( "Can't emulate on error clause on H2" );
}
if ( arguments.emptyBehavior() == JsonValueEmptyBehavior.ERROR ) {
throw new QueryException( "Can't emulate error on empty clause on H2" );
}
final Expression defaultExpression = arguments.emptyBehavior() == null
? null
: arguments.emptyBehavior().getDefaultExpression();
if ( arguments.returningType() != null ) {
sqlAppender.appendSql( "cast(" );
}
final String jsonPath;
try {
jsonPath = walker.getLiteralValue( arguments.jsonPath() );
}
catch (Exception ex) {
throw new QueryException( "H2 json_value only support literal json paths, but got " + arguments.jsonPath() );
}
sqlAppender.appendSql( "stringdecode(btrim(nullif(" );
if ( defaultExpression != null ) {
sqlAppender.appendSql( "coalesce(" );
}
renderJsonPath( sqlAppender, arguments.jsonDocument(), walker, jsonPath );
if ( defaultExpression != null ) {
sqlAppender.appendSql( ",cast(" );
defaultExpression.accept( walker );
sqlAppender.appendSql( " as varchar))" );
}
sqlAppender.appendSql( ",'null'),'\"'))");
if ( arguments.returningType() != null ) {
sqlAppender.appendSql( " as " );
arguments.returningType().accept( walker );
sqlAppender.appendSql( ')' );
}
}
private void renderJsonPath(
SqlAppender sqlAppender,
SqlAstNode jsonDocument,
SqlAstTranslator<?> walker,
String jsonPath) {
sqlAppender.appendSql( "cast(" );
final List<JsonPathHelper.JsonPathElement> jsonPathElements = JsonPathHelper.parseJsonPathElements( jsonPath );
final boolean needsWrapping = jsonPathElements.get( 0 ) instanceof JsonPathHelper.JsonAttribute;
if ( needsWrapping ) {
sqlAppender.appendSql( '(' );
}
jsonDocument.accept( walker );
if ( needsWrapping ) {
sqlAppender.appendSql( ')' );
}
for ( int i = 0; i < jsonPathElements.size(); i++ ) {
final JsonPathHelper.JsonPathElement jsonPathElement = jsonPathElements.get( i );
if ( jsonPathElement instanceof JsonPathHelper.JsonAttribute attribute ) {
final String attributeName = attribute.attribute();
appendInDoubleQuotes( sqlAppender, attributeName );
}
else {
sqlAppender.appendSql( '[' );
sqlAppender.appendSql( ( (JsonPathHelper.JsonIndexAccess) jsonPathElement ).index() + 1 );
sqlAppender.appendSql( ']' );
}
}
sqlAppender.appendSql( " as varchar)" );
}
private static void appendInDoubleQuotes(SqlAppender sqlAppender, String attributeName) {
sqlAppender.appendSql( ".\"" );
for ( int j = 0; j < attributeName.length(); j++ ) {
final char c = attributeName.charAt( j );
if ( c == '"' ) {
sqlAppender.appendSql( '"' );
}
sqlAppender.appendSql( c );
}
sqlAppender.appendSql( '"' );
}
}

View File

@ -0,0 +1,61 @@
/*
* 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.ArrayList;
import java.util.List;
import org.hibernate.QueryException;
public class JsonPathHelper {
public static List<JsonPathElement> parseJsonPathElements(String jsonPath) {
if ( jsonPath.charAt( 0 ) != '$' || jsonPath.charAt( 1 ) != '.' ) {
throw new QueryException( "Json path expression expression emulation only supports absolute paths i.e. must start with a '$.' but got: " + jsonPath );
}
final var jsonPathElements = new ArrayList<JsonPathElement>();
int startIndex = 2;
int dotIndex;
try {
while ( ( dotIndex = jsonPath.indexOf( '.', startIndex ) ) != -1 ) {
parseAttribute( jsonPath, startIndex, dotIndex, jsonPathElements );
startIndex = dotIndex + 1;
}
parseAttribute( jsonPath, startIndex, jsonPath.length(), jsonPathElements );
}
catch (Exception ex) {
throw new QueryException( "Can't emulate non-simple json path expression: " + jsonPath, ex );
}
return jsonPathElements;
}
private static void parseAttribute(String jsonPath, int startIndex, int endIndex, ArrayList<JsonPathElement> jsonPathElements) {
final int bracketIndex = jsonPath.indexOf( '[', startIndex );
if ( bracketIndex != -1 && bracketIndex < endIndex ) {
jsonPathElements.add( new JsonAttribute( jsonPath.substring( startIndex, bracketIndex ) ) );
parseBracket( jsonPath, bracketIndex, endIndex, jsonPathElements );
}
else {
jsonPathElements.add( new JsonAttribute( jsonPath.substring( startIndex, endIndex ) ) );
}
}
private static void parseBracket(String jsonPath, int bracketStartIndex, int endIndex, ArrayList<JsonPathElement> jsonPathElements) {
assert jsonPath.charAt( bracketStartIndex ) == '[';
final int bracketEndIndex = jsonPath.lastIndexOf( ']', endIndex );
if ( bracketEndIndex < bracketStartIndex ) {
throw new QueryException( "Can't emulate non-simple json path expression: " + jsonPath );
}
final int index = Integer.parseInt( jsonPath, bracketStartIndex + 1, bracketEndIndex, 10 );
jsonPathElements.add( new JsonIndexAccess( index ) );
}
public sealed interface JsonPathElement {}
public record JsonAttribute(String attribute) implements JsonPathElement {}
public record JsonIndexAccess(int index) implements JsonPathElement {}
}

View File

@ -0,0 +1,176 @@
/*
* 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.query.spi.QueryEngine;
import org.hibernate.query.sqm.function.AbstractSqmSelfRenderingFunctionDescriptor;
import org.hibernate.query.sqm.function.FunctionKind;
import org.hibernate.query.sqm.function.SelfRenderingSqmFunction;
import org.hibernate.query.sqm.produce.function.ArgumentTypesValidator;
import org.hibernate.query.sqm.produce.function.StandardArgumentsValidators;
import org.hibernate.query.sqm.produce.function.StandardFunctionArgumentTypeResolvers;
import org.hibernate.query.sqm.tree.SqmTypedNode;
import org.hibernate.query.sqm.tree.expression.SqmJsonValueExpression;
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.sql.ast.tree.expression.JsonValueEmptyBehavior;
import org.hibernate.sql.ast.tree.expression.JsonValueErrorBehavior;
import org.hibernate.type.spi.TypeConfiguration;
import org.checkerframework.checker.nullness.qual.Nullable;
import static org.hibernate.query.sqm.produce.function.FunctionParameterType.ANY;
import static org.hibernate.query.sqm.produce.function.FunctionParameterType.IMPLICIT_JSON;
import static org.hibernate.query.sqm.produce.function.FunctionParameterType.JSON;
import static org.hibernate.query.sqm.produce.function.FunctionParameterType.STRING;
/**
* Standard json_value function.
*/
public class JsonValueFunction extends AbstractSqmSelfRenderingFunctionDescriptor {
protected final boolean supportsJsonPathExpression;
public JsonValueFunction(TypeConfiguration typeConfiguration, boolean supportsJsonPathExpression) {
super(
"json_value",
FunctionKind.NORMAL,
StandardArgumentsValidators.composite(
new ArgumentTypesValidator( StandardArgumentsValidators.between( 2, 5 ), IMPLICIT_JSON, STRING, ANY, ANY, ANY )
),
new CastTargetReturnTypeResolver( typeConfiguration ),
StandardFunctionArgumentTypeResolvers.invariant( typeConfiguration, JSON, STRING )
);
this.supportsJsonPathExpression = supportsJsonPathExpression;
}
@Override
protected <T> SelfRenderingSqmFunction<T> generateSqmFunctionExpression(
List<? extends SqmTypedNode<?>> arguments,
ReturnableType<T> impliedResultType,
QueryEngine queryEngine) {
return new SqmJsonValueExpression<>(
this,
this,
arguments,
impliedResultType,
getArgumentsValidator(),
getReturnTypeResolver(),
queryEngine.getCriteriaBuilder(),
getName()
);
}
@Override
public void render(
SqlAppender sqlAppender,
List<? extends SqlAstNode> sqlAstArguments,
ReturnableType<?> returnType,
SqlAstTranslator<?> walker) {
render( sqlAppender, JsonValueArguments.extract( sqlAstArguments ), returnType, walker );
}
protected void render(
SqlAppender sqlAppender,
JsonValueArguments arguments,
ReturnableType<?> returnType,
SqlAstTranslator<?> walker) {
sqlAppender.appendSql( "json_value(" );
arguments.jsonDocument().accept( walker );
sqlAppender.appendSql( ',' );
if ( supportsJsonPathExpression ) {
arguments.jsonPath().accept( walker );
}
else {
walker.getSessionFactory().getJdbcServices().getDialect().appendLiteral(
sqlAppender,
walker.getLiteralValue( arguments.jsonPath() )
);
}
if ( arguments.returningType() != null ) {
sqlAppender.appendSql( " returning " );
arguments.returningType().accept( walker );
}
if ( arguments.errorBehavior() != null ) {
if ( arguments.errorBehavior() == JsonValueErrorBehavior.ERROR ) {
sqlAppender.appendSql( " error on error" );
}
else if ( arguments.errorBehavior() != JsonValueErrorBehavior.NULL ) {
final Expression defaultExpression = arguments.errorBehavior().getDefaultExpression();
assert defaultExpression != null;
sqlAppender.appendSql( " default " );
defaultExpression.accept( walker );
sqlAppender.appendSql( " on error" );
}
}
if ( arguments.emptyBehavior() != null ) {
if ( arguments.emptyBehavior() == JsonValueEmptyBehavior.ERROR ) {
sqlAppender.appendSql( " error on empty" );
}
else if ( arguments.emptyBehavior() != JsonValueEmptyBehavior.NULL ) {
final Expression defaultExpression = arguments.emptyBehavior().getDefaultExpression();
assert defaultExpression != null;
sqlAppender.appendSql( " default " );
defaultExpression.accept( walker );
sqlAppender.appendSql( " on empty" );
}
}
sqlAppender.appendSql( ')' );
}
protected record JsonValueArguments(
Expression jsonDocument,
Expression jsonPath,
boolean isJsonType,
@Nullable CastTarget returningType,
@Nullable JsonValueErrorBehavior errorBehavior,
@Nullable JsonValueEmptyBehavior emptyBehavior) {
public static JsonValueArguments extract(List<? extends SqlAstNode> sqlAstArguments) {
int nextIndex = 2;
CastTarget castTarget = null;
JsonValueErrorBehavior errorBehavior = null;
JsonValueEmptyBehavior emptyBehavior = null;
if ( nextIndex < sqlAstArguments.size() ) {
final SqlAstNode node = sqlAstArguments.get( nextIndex );
if ( node instanceof CastTarget ) {
castTarget = (CastTarget) node;
nextIndex++;
}
}
if ( nextIndex < sqlAstArguments.size() ) {
final SqlAstNode node = sqlAstArguments.get( nextIndex );
if ( node instanceof JsonValueErrorBehavior ) {
errorBehavior = (JsonValueErrorBehavior) node;
nextIndex++;
}
}
if ( nextIndex < sqlAstArguments.size() ) {
final SqlAstNode node = sqlAstArguments.get( nextIndex );
if ( node instanceof JsonValueEmptyBehavior ) {
emptyBehavior = (JsonValueEmptyBehavior) node;
}
}
final Expression jsonDocument = (Expression) sqlAstArguments.get( 0 );
return new JsonValueArguments(
jsonDocument,
(Expression) sqlAstArguments.get( 1 ),
jsonDocument.getExpressionType() != null
&& jsonDocument.getExpressionType().getSingleJdbcMapping().getJdbcType().isJson(),
castTarget,
errorBehavior,
emptyBehavior
);
}
}
}

View File

@ -0,0 +1,53 @@
/*
* 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.QueryException;
import org.hibernate.query.ReturnableType;
import org.hibernate.sql.ast.SqlAstTranslator;
import org.hibernate.sql.ast.spi.SqlAppender;
import org.hibernate.sql.ast.tree.expression.JsonValueEmptyBehavior;
import org.hibernate.sql.ast.tree.expression.JsonValueErrorBehavior;
import org.hibernate.type.spi.TypeConfiguration;
/**
* MariaDB json_value function.
*/
public class MariaDBJsonValueFunction extends JsonValueFunction {
public MariaDBJsonValueFunction(TypeConfiguration typeConfiguration) {
super( typeConfiguration, true );
}
@Override
protected void render(
SqlAppender sqlAppender,
JsonValueArguments arguments,
ReturnableType<?> returnType,
SqlAstTranslator<?> walker) {
if ( arguments.errorBehavior() != null && arguments.errorBehavior() != JsonValueErrorBehavior.NULL ) {
// MariaDB reports the error 4038 as warning and simply returns null
throw new QueryException( "Can't emulate on error clause on MariaDB" );
}
if ( arguments.emptyBehavior() != null && arguments.emptyBehavior() != JsonValueEmptyBehavior.NULL ) {
throw new QueryException( "Can't emulate on empty clause on MariaDB" );
}
if ( arguments.returningType() != null ) {
sqlAppender.append( "cast(" );
}
sqlAppender.appendSql( "json_unquote(nullif(json_extract(" );
arguments.jsonDocument().accept( walker );
sqlAppender.appendSql( "," );
arguments.jsonPath().accept( walker );
sqlAppender.appendSql( "),'null'))" );
if ( arguments.returningType() != null ) {
sqlAppender.appendSql( " as " );
arguments.returningType().accept( walker );
sqlAppender.appendSql( ')' );
}
}
}

View File

@ -0,0 +1,54 @@
/*
* 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.ReturnableType;
import org.hibernate.sql.ast.SqlAstTranslator;
import org.hibernate.sql.ast.spi.SqlAppender;
import org.hibernate.sql.ast.tree.expression.JsonValueEmptyBehavior;
import org.hibernate.sql.ast.tree.expression.JsonValueErrorBehavior;
import org.hibernate.type.spi.TypeConfiguration;
/**
* MySQL json_value function.
*/
public class MySQLJsonValueFunction extends JsonValueFunction {
public MySQLJsonValueFunction(TypeConfiguration typeConfiguration) {
super( typeConfiguration, true );
}
@Override
protected void render(
SqlAppender sqlAppender,
JsonValueArguments arguments,
ReturnableType<?> returnType,
SqlAstTranslator<?> walker) {
// json_extract errors by default
if ( arguments.errorBehavior() != null && arguments.errorBehavior() != JsonValueErrorBehavior.ERROR
|| arguments.emptyBehavior() == JsonValueEmptyBehavior.ERROR
// Can't emulate DEFAULT ON EMPTY since we can't differentiate between a NULL value and EMPTY
|| arguments.emptyBehavior() != null && arguments.emptyBehavior() != JsonValueEmptyBehavior.NULL ) {
super.render( sqlAppender, arguments, returnType, walker );
}
else {
if ( arguments.returningType() != null ) {
sqlAppender.append( "cast(" );
}
sqlAppender.appendSql( "json_unquote(nullif(json_extract(" );
arguments.jsonDocument().accept( walker );
sqlAppender.appendSql( "," );
arguments.jsonPath().accept( walker );
sqlAppender.appendSql( "),cast('null' as json)))" );
if ( arguments.returningType() != null ) {
sqlAppender.appendSql( " as " );
arguments.returningType().accept( walker );
sqlAppender.appendSql( ')' );
}
}
}
}

View File

@ -0,0 +1,72 @@
/*
* 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.QueryException;
import org.hibernate.query.ReturnableType;
import org.hibernate.sql.ast.SqlAstTranslator;
import org.hibernate.sql.ast.spi.SqlAppender;
import org.hibernate.sql.ast.tree.SqlAstNode;
import org.hibernate.sql.ast.tree.expression.JdbcParameter;
import org.hibernate.sql.ast.tree.expression.JsonValueEmptyBehavior;
import org.hibernate.sql.ast.tree.expression.JsonValueErrorBehavior;
import org.hibernate.sql.ast.tree.expression.Literal;
import org.hibernate.type.spi.TypeConfiguration;
/**
* PostgreSQL json_value function.
*/
public class PostgreSQLJsonValueFunction extends JsonValueFunction {
public PostgreSQLJsonValueFunction(TypeConfiguration typeConfiguration) {
super( typeConfiguration, true );
}
@Override
protected void render(
SqlAppender sqlAppender,
JsonValueArguments arguments,
ReturnableType<?> returnType,
SqlAstTranslator<?> walker) {
// jsonb_path_query_first errors by default
if ( arguments.errorBehavior() != null && arguments.errorBehavior() != JsonValueErrorBehavior.ERROR ) {
throw new QueryException( "Can't emulate on error clause on PostgreSQL" );
}
if ( arguments.emptyBehavior() != null && arguments.emptyBehavior() != JsonValueEmptyBehavior.NULL ) {
throw new QueryException( "Can't emulate on empty clause on PostgreSQL" );
}
if ( arguments.returningType() != null ) {
sqlAppender.appendSql( "cast(" );
}
sqlAppender.appendSql( "jsonb_path_query_first(" );
final boolean needsCast = !arguments.isJsonType() && arguments.jsonDocument() instanceof JdbcParameter;
if ( needsCast ) {
sqlAppender.appendSql( "cast(" );
}
arguments.jsonDocument().accept( walker );
if ( needsCast ) {
sqlAppender.appendSql( " as jsonb)" );
}
sqlAppender.appendSql( ',' );
final SqlAstNode jsonPath = arguments.jsonPath();
if ( jsonPath instanceof Literal ) {
jsonPath.accept( walker );
}
else {
sqlAppender.appendSql( "cast(" );
jsonPath.accept( walker );
sqlAppender.appendSql( " as jsonpath)" );
}
// Unquote the value
sqlAppender.appendSql( ")#>>'{}'" );
if ( arguments.returningType() != null ) {
sqlAppender.appendSql( " as " );
arguments.returningType().accept( walker );
sqlAppender.appendSql( ')' );
}
}
}

View File

@ -0,0 +1,58 @@
/*
* 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.QueryException;
import org.hibernate.query.ReturnableType;
import org.hibernate.sql.ast.SqlAstTranslator;
import org.hibernate.sql.ast.spi.SqlAppender;
import org.hibernate.sql.ast.tree.expression.Expression;
import org.hibernate.sql.ast.tree.expression.JsonValueEmptyBehavior;
import org.hibernate.sql.ast.tree.expression.JsonValueErrorBehavior;
import org.hibernate.type.spi.TypeConfiguration;
/**
* SQL Server json_value function.
*/
public class SQLServerJsonValueFunction extends JsonValueFunction {
public SQLServerJsonValueFunction(TypeConfiguration typeConfiguration) {
super( typeConfiguration, true );
}
@Override
protected void render(
SqlAppender sqlAppender,
JsonValueArguments arguments,
ReturnableType<?> returnType,
SqlAstTranslator<?> walker) {
// openjson errors by default
if ( arguments.errorBehavior() != null && arguments.errorBehavior() != JsonValueErrorBehavior.ERROR ) {
throw new QueryException( "Can't emulate on error clause on SQL server" );
}
sqlAppender.appendSql( "(select v from openjson(" );
arguments.jsonDocument().accept( walker );
sqlAppender.appendSql( ",'$') with (v " );
if ( arguments.returningType() != null ) {
arguments.returningType().accept( walker );
}
else {
sqlAppender.appendSql( "varchar(max)" );
}
sqlAppender.appendSql( ' ' );
if ( arguments.emptyBehavior() != null && arguments.emptyBehavior() != JsonValueEmptyBehavior.NULL ) {
walker.getSessionFactory().getJdbcServices().getDialect().appendLiteral(
sqlAppender,
"strict " + walker.getLiteralValue( arguments.jsonPath() )
);
}
else {
arguments.jsonPath().accept( walker );
}
sqlAppender.appendSql( "))" );
}
}

View File

@ -3680,6 +3680,38 @@ public interface HibernateCriteriaBuilder extends CriteriaBuilder {
@Incubating
<E> JpaPredicate collectionIntersectsNullable(Collection<E> collection1, Expression<? extends Collection<? extends E>> collectionExpression2);
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// JSON functions
/**
* @see #jsonValue(Expression, String, Class)
* @since 7.0
*/
@Incubating
JpaJsonValueExpression<String> jsonValue(Expression<?> jsonDocument, String jsonPath);
/**
* Extracts a value by JSON path from a json document.
*
* @since 7.0
*/
@Incubating
<T> JpaJsonValueExpression<T> jsonValue(Expression<?> jsonDocument, String jsonPath, Class<T> returningType);
/**
* @see #jsonValue(Expression, Expression, Class)
* @since 7.0
*/
@Incubating
JpaJsonValueExpression<String> jsonValue(Expression<?> jsonDocument, Expression<String> jsonPath);
/**
* Extracts a value by JSON path from a json document.
*
* @since 7.0
*/
@Incubating
<T> JpaJsonValueExpression<T> jsonValue(Expression<?> jsonDocument, Expression<String> jsonPath, Class<T> returningType);
@Override
JpaPredicate and(List<Predicate> restrictions);

View File

@ -0,0 +1,142 @@
/*
* 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.query.criteria;
import org.hibernate.Incubating;
import jakarta.persistence.criteria.Expression;
import org.checkerframework.checker.nullness.qual.Nullable;
/**
* A special expression for the {@code json_value} function.
* @since 7.0
*/
@Incubating
public interface JpaJsonValueExpression<T> extends JpaExpression<T> {
/**
* Get the {@link ErrorBehavior} of this json value expression.
*
* @return the error behavior
*/
ErrorBehavior getErrorBehavior();
/**
* Get the {@link EmptyBehavior} of this json value expression.
*
* @return the empty behavior
*/
EmptyBehavior getEmptyBehavior();
/**
* Get the {@link JpaExpression} that is returned on a json processing error.
* Returns {@code null} if {@link #getErrorBehavior()} is not {@link ErrorBehavior#DEFAULT}.
*
* @return the value to return on a json processing error
*/
@Nullable JpaExpression<T> getErrorDefault();
/**
* Get the {@link JpaExpression} that is returned when the JSON path does not resolve for a JSON document.
* Returns {@code null} if {@link #getEmptyBehavior()} is not {@link EmptyBehavior#DEFAULT}.
*
* @return the value to return on a json processing error
*/
@Nullable JpaExpression<T> getEmptyDefault();
/**
* Sets the {@link ErrorBehavior#UNSPECIFIED} for this json value expression.
*
* @return {@code this} for method chaining
*/
JpaJsonValueExpression<T> unspecifiedOnError();
/**
* Sets the {@link ErrorBehavior#ERROR} for this json value expression.
*
* @return {@code this} for method chaining
*/
JpaJsonValueExpression<T> errorOnError();
/**
* Sets the {@link ErrorBehavior#NULL} for this json value expression.
*
* @return {@code this} for method chaining
*/
JpaJsonValueExpression<T> nullOnError();
/**
* Sets the {@link ErrorBehavior#DEFAULT} for this json value expression.
*
* @return {@code this} for method chaining
*/
JpaJsonValueExpression<T> defaultOnError(Expression<?> expression);
/**
* Sets the {@link EmptyBehavior#UNSPECIFIED} for this json value expression.
*
* @return {@code this} for method chaining
*/
JpaJsonValueExpression<T> unspecifiedOnEmpty();
/**
* Sets the {@link EmptyBehavior#ERROR} for this json value expression.
*
* @return {@code this} for method chaining
*/
JpaJsonValueExpression<T> errorOnEmpty();
/**
* Sets the {@link EmptyBehavior#NULL} for this json value expression.
*
* @return {@code this} for method chaining
*/
JpaJsonValueExpression<T> nullOnEmpty();
/**
* Sets the {@link EmptyBehavior#DEFAULT} for this json value expression.
*
* @return {@code this} for method chaining
*/
JpaJsonValueExpression<T> defaultOnEmpty(Expression<?> expression);
/**
* The behavior of the json value expression when a JSON processing error occurs.
*/
enum ErrorBehavior {
/**
* SQL/JDBC error should be raised.
*/
ERROR,
/**
* {@code null} should be returned.
*/
NULL,
/**
* The {@link JpaJsonValueExpression#getErrorDefault()} value should be returned.
*/
DEFAULT,
/**
* Unspecified behavior i.e. the default database behavior.
*/
UNSPECIFIED
}
/**
* The behavior of the json value expression when a JSON path does not resolve for a JSON document.
*/
enum EmptyBehavior {
/**
* SQL/JDBC error should be raised.
*/
ERROR,
/**
* {@code null} should be returned.
*/
NULL,
/**
* The {@link JpaJsonValueExpression#getEmptyDefault()} value should be returned.
*/
DEFAULT,
/**
* Unspecified behavior i.e. the default database behavior.
*/
UNSPECIFIED
}
}

View File

@ -38,6 +38,7 @@ import org.hibernate.query.criteria.JpaExpression;
import org.hibernate.query.criteria.JpaFunction;
import org.hibernate.query.criteria.JpaInPredicate;
import org.hibernate.query.criteria.JpaJoin;
import org.hibernate.query.criteria.JpaJsonValueExpression;
import org.hibernate.query.criteria.JpaListJoin;
import org.hibernate.query.criteria.JpaMapJoin;
import org.hibernate.query.criteria.JpaOrder;
@ -3343,4 +3344,34 @@ public class HibernateCriteriaBuilderDelegate implements HibernateCriteriaBuilde
Expression<? extends Collection<? extends E>> collectionExpression2) {
return criteriaBuilder.collectionIntersectsNullable( collection1, collectionExpression2 );
}
@Override
@Incubating
public JpaJsonValueExpression<String> jsonValue(Expression<?> jsonDocument, String jsonPath) {
return criteriaBuilder.jsonValue( jsonDocument, jsonPath );
}
@Override
@Incubating
public <T> JpaJsonValueExpression<T> jsonValue(
Expression<?> jsonDocument,
String jsonPath,
Class<T> returningType) {
return criteriaBuilder.jsonValue( jsonDocument, jsonPath, returningType );
}
@Override
@Incubating
public JpaJsonValueExpression<String> jsonValue(Expression<?> jsonDocument, Expression<String> jsonPath) {
return criteriaBuilder.jsonValue( jsonDocument, jsonPath );
}
@Override
@Incubating
public <T> JpaJsonValueExpression<T> jsonValue(
Expression<?> jsonDocument,
Expression<String> jsonPath,
Class<T> returningType) {
return criteriaBuilder.jsonValue( jsonDocument, jsonPath, returningType );
}
}

View File

@ -143,6 +143,7 @@ import org.hibernate.query.sqm.tree.expression.SqmExtractUnit;
import org.hibernate.query.sqm.tree.expression.SqmFormat;
import org.hibernate.query.sqm.tree.expression.SqmFunction;
import org.hibernate.query.sqm.tree.expression.SqmHqlNumericLiteral;
import org.hibernate.query.sqm.tree.expression.SqmJsonValueExpression;
import org.hibernate.query.sqm.tree.expression.SqmLiteral;
import org.hibernate.query.sqm.tree.expression.SqmLiteralEntityType;
import org.hibernate.query.sqm.tree.expression.SqmLiteralNull;
@ -2691,6 +2692,45 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
return new SqmBooleanExpressionPredicate( contains, negated, creationContext.getNodeBuilder() );
}
@Override
public SqmExpression<?> visitJsonValueFunction(HqlParser.JsonValueFunctionContext 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();
final SqmCastTarget<?> castTarget = returningClause == null
? null
: (SqmCastTarget<?>) returningClause.castTarget().accept( this );
final SqmJsonValueExpression<?> jsonValue = (SqmJsonValueExpression<?>) getFunctionDescriptor( "json_value" ).generateSqmExpression(
castTarget == null
? asList( jsonDocument, jsonPath )
: asList( jsonDocument, jsonPath, castTarget ),
null,
creationContext.getQueryEngine()
);
for ( HqlParser.JsonValueOnErrorOrEmptyClauseContext subCtx : ctx.jsonValueOnErrorOrEmptyClause() ) {
final TerminalNode firstToken = (TerminalNode) subCtx.getChild( 0 );
final TerminalNode lastToken = (TerminalNode) subCtx.getChild( subCtx.getChildCount() - 1 );
if ( lastToken.getSymbol().getType() == HqlParser.ERROR ) {
switch ( firstToken.getSymbol().getType() ) {
case HqlParser.NULL -> jsonValue.nullOnError();
case HqlParser.ERROR -> jsonValue.errorOnError();
case HqlParser.DEFAULT ->
jsonValue.defaultOnError( (SqmExpression<?>) subCtx.expression().accept( this ) );
}
}
else {
switch ( firstToken.getSymbol().getType() ) {
case HqlParser.NULL -> jsonValue.nullOnEmpty();
case HqlParser.ERROR -> jsonValue.errorOnEmpty();
case HqlParser.DEFAULT ->
jsonValue.defaultOnEmpty( (SqmExpression<?>) subCtx.expression().accept( this ) );
}
}
}
return jsonValue;
}
@Override
public SqmPredicate visitIncludesPredicate(HqlParser.IncludesPredicateContext ctx) {
final boolean negated = ctx.NOT() != null;

View File

@ -44,6 +44,7 @@ import org.hibernate.query.sqm.tree.domain.SqmSetJoin;
import org.hibernate.query.sqm.tree.domain.SqmSingularJoin;
import org.hibernate.query.sqm.tree.expression.SqmExpression;
import org.hibernate.query.sqm.tree.expression.SqmFunction;
import org.hibernate.query.sqm.tree.expression.SqmJsonValueExpression;
import org.hibernate.query.sqm.tree.expression.SqmModifiedSubQueryExpression;
import org.hibernate.query.sqm.tree.expression.SqmTuple;
import org.hibernate.query.sqm.tree.from.SqmRoot;
@ -613,6 +614,21 @@ public interface NodeBuilder extends HibernateCriteriaBuilder, BindingContext {
@Override
<E> SqmPredicate collectionIntersectsNullable(Collection<E> collection1, Expression<? extends Collection<? extends E>> collectionExpression2);
@Override
<T> SqmJsonValueExpression<T> jsonValue(
Expression<?> jsonDocument,
Expression<String> jsonPath,
Class<T> returningType);
@Override
SqmJsonValueExpression<String> jsonValue(Expression<?> jsonDocument, Expression<String> jsonPath);
@Override
<T> SqmJsonValueExpression<T> jsonValue(Expression<?> jsonDocument, String jsonPath, Class<T> returningType);
@Override
SqmJsonValueExpression<String> jsonValue(Expression<?> jsonDocument, String jsonPath);
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Covariant overrides

View File

@ -120,6 +120,7 @@ import org.hibernate.query.sqm.tree.expression.SqmExpression;
import org.hibernate.query.sqm.tree.expression.SqmExtractUnit;
import org.hibernate.query.sqm.tree.expression.SqmFormat;
import org.hibernate.query.sqm.tree.expression.SqmFunction;
import org.hibernate.query.sqm.tree.expression.SqmJsonValueExpression;
import org.hibernate.query.sqm.tree.expression.SqmLiteral;
import org.hibernate.query.sqm.tree.expression.SqmLiteralNull;
import org.hibernate.query.sqm.tree.expression.SqmModifiedSubQueryExpression;
@ -5289,4 +5290,45 @@ public class SqmCriteriaNodeBuilder implements NodeBuilder, Serializable {
queryEngine
);
}
@Override
public SqmJsonValueExpression<String> jsonValue(Expression<?> jsonDocument, String jsonPath) {
return jsonValue( jsonDocument, value( jsonPath ), null );
}
@Override
public <T> SqmJsonValueExpression<T> jsonValue(
Expression<?> jsonDocument,
String jsonPath,
Class<T> returningType) {
return jsonValue( jsonDocument, value( jsonPath ), returningType );
}
@Override
public SqmJsonValueExpression<String> jsonValue(Expression<?> jsonDocument, Expression<String> jsonPath) {
return jsonValue( jsonDocument, jsonPath, null );
}
@Override
@SuppressWarnings("unchecked")
public <T> SqmJsonValueExpression<T> jsonValue(
Expression<?> jsonDocument,
Expression<String> jsonPath,
Class<T> returningType) {
if ( returningType == null ) {
return (SqmJsonValueExpression<T>) getFunctionDescriptor( "json_value" ).generateSqmExpression(
asList( (SqmTypedNode<?>) jsonDocument, (SqmTypedNode<?>) jsonPath ),
null,
queryEngine
);
}
else {
final BasicType<T> type = getTypeConfiguration().standardBasicTypeForJavaType( returningType );
return (SqmJsonValueExpression<T>) getFunctionDescriptor( "json_value" ).generateSqmExpression(
asList( (SqmTypedNode<?>) jsonDocument, (SqmTypedNode<?>) jsonPath, new SqmCastTarget<>( type, this ) ),
type,
queryEngine
);
}
}
}

View File

@ -248,6 +248,10 @@ public class ArgumentTypesValidator implements ArgumentsValidator {
case DATE -> jdbcType.hasDatePart();
case TIME -> jdbcType.hasTimePart();
case SPATIAL -> jdbcType.isSpatial();
case JSON:
return jdbcType.isJson();
case IMPLICIT_JSON:
return jdbcType.isImplicitJson();
default -> true; // TODO: should we throw here?
};
}

View File

@ -86,6 +86,18 @@ public enum FunctionParameterType {
* @see org.hibernate.type.SqlTypes#isSpatialType(int)
*/
SPATIAL,
/**
* Indicates that the argument should be a JSON type
* @see org.hibernate.type.SqlTypes#isJsonType(int)
* @since 7.0
*/
JSON,
/**
* Indicates that the argument should be a JSON or String type
* @see org.hibernate.type.SqlTypes#isImplicitJsonType(int)
* @since 7.0
*/
IMPLICIT_JSON,
/**
* Indicates a parameter that accepts any type, except untyped expressions like {@code null} literals
*/

View File

@ -0,0 +1,287 @@
/*
* 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.query.sqm.tree.expression;
import java.util.ArrayList;
import java.util.List;
import org.hibernate.Incubating;
import org.hibernate.query.ReturnableType;
import org.hibernate.query.criteria.JpaExpression;
import org.hibernate.query.criteria.JpaJsonValueExpression;
import org.hibernate.query.sqm.NodeBuilder;
import org.hibernate.query.sqm.function.FunctionRenderer;
import org.hibernate.query.sqm.function.SelfRenderingFunctionSqlAstExpression;
import org.hibernate.query.sqm.function.SelfRenderingSqmFunction;
import org.hibernate.query.sqm.function.SqmFunctionDescriptor;
import org.hibernate.query.sqm.produce.function.ArgumentsValidator;
import org.hibernate.query.sqm.produce.function.FunctionReturnTypeResolver;
import org.hibernate.query.sqm.sql.SqmToSqlAstConverter;
import org.hibernate.query.sqm.tree.SqmCopyContext;
import org.hibernate.query.sqm.tree.SqmTypedNode;
import org.hibernate.sql.ast.tree.SqlAstNode;
import org.hibernate.sql.ast.tree.expression.Expression;
import org.hibernate.sql.ast.tree.expression.JsonValueEmptyBehavior;
import org.hibernate.sql.ast.tree.expression.JsonValueErrorBehavior;
import org.checkerframework.checker.nullness.qual.Nullable;
/**
* Special expression for the json_value function that also captures special syntax elements like error and empty behavior.
*
* @since 7.0
*/
@Incubating
public class SqmJsonValueExpression<T> extends SelfRenderingSqmFunction<T> implements JpaJsonValueExpression<T> {
private @Nullable ErrorBehavior errorBehavior;
private SqmExpression<T> errorDefaultExpression;
private @Nullable EmptyBehavior emptyBehavior;
private SqmExpression<T> emptyDefaultExpression;
public SqmJsonValueExpression(
SqmFunctionDescriptor descriptor,
FunctionRenderer renderer,
List<? extends SqmTypedNode<?>> arguments,
@Nullable ReturnableType<T> impliedResultType,
@Nullable ArgumentsValidator argumentsValidator,
FunctionReturnTypeResolver returnTypeResolver,
NodeBuilder nodeBuilder,
String name) {
super(
descriptor,
renderer,
arguments,
impliedResultType,
argumentsValidator,
returnTypeResolver,
nodeBuilder,
name
);
}
private SqmJsonValueExpression(
SqmFunctionDescriptor descriptor,
FunctionRenderer renderer,
List<? extends SqmTypedNode<?>> arguments,
@Nullable ReturnableType<T> impliedResultType,
@Nullable ArgumentsValidator argumentsValidator,
FunctionReturnTypeResolver returnTypeResolver,
NodeBuilder nodeBuilder,
String name,
@Nullable ErrorBehavior errorBehavior,
SqmExpression<T> errorDefaultExpression,
@Nullable EmptyBehavior emptyBehavior,
SqmExpression<T> emptyDefaultExpression) {
super(
descriptor,
renderer,
arguments,
impliedResultType,
argumentsValidator,
returnTypeResolver,
nodeBuilder,
name
);
this.errorBehavior = errorBehavior;
this.errorDefaultExpression = errorDefaultExpression;
this.emptyBehavior = emptyBehavior;
this.emptyDefaultExpression = emptyDefaultExpression;
}
public SqmJsonValueExpression<T> copy(SqmCopyContext context) {
final SqmJsonValueExpression<T> existing = context.getCopy( this );
if ( existing != null ) {
return existing;
}
final List<SqmTypedNode<?>> arguments = new ArrayList<>( getArguments().size() );
for ( SqmTypedNode<?> argument : getArguments() ) {
arguments.add( argument.copy( context ) );
}
return context.registerCopy(
this,
new SqmJsonValueExpression<>(
getFunctionDescriptor(),
getFunctionRenderer(),
arguments,
getImpliedResultType(),
getArgumentsValidator(),
getReturnTypeResolver(),
nodeBuilder(),
getFunctionName(),
errorBehavior,
errorDefaultExpression == null ? null : errorDefaultExpression.copy( context ),
emptyBehavior,
emptyDefaultExpression == null ? null : emptyDefaultExpression.copy( context )
)
);
}
@Override
public ErrorBehavior getErrorBehavior() {
return errorBehavior;
}
@Override
public EmptyBehavior getEmptyBehavior() {
return emptyBehavior;
}
@Override
public @Nullable JpaExpression<T> getErrorDefault() {
return errorDefaultExpression;
}
@Override
public @Nullable JpaExpression<T> getEmptyDefault() {
return emptyDefaultExpression;
}
@Override
public JpaJsonValueExpression<T> unspecifiedOnError() {
this.errorBehavior = ErrorBehavior.UNSPECIFIED;
this.errorDefaultExpression = null;
return this;
}
@Override
public JpaJsonValueExpression<T> errorOnError() {
this.errorBehavior = ErrorBehavior.ERROR;
this.errorDefaultExpression = null;
return this;
}
@Override
public JpaJsonValueExpression<T> nullOnError() {
this.errorBehavior = ErrorBehavior.NULL;
this.errorDefaultExpression = null;
return this;
}
@Override
public JpaJsonValueExpression<T> defaultOnError(jakarta.persistence.criteria.Expression<?> expression) {
this.errorBehavior = ErrorBehavior.DEFAULT;
//noinspection unchecked
this.errorDefaultExpression = (SqmExpression<T>) expression;
return this;
}
@Override
public JpaJsonValueExpression<T> unspecifiedOnEmpty() {
this.errorBehavior = ErrorBehavior.UNSPECIFIED;
this.errorDefaultExpression = null;
return this;
}
@Override
public JpaJsonValueExpression<T> errorOnEmpty() {
this.emptyBehavior = EmptyBehavior.ERROR;
this.emptyDefaultExpression = null;
return this;
}
@Override
public JpaJsonValueExpression<T> nullOnEmpty() {
this.emptyBehavior = EmptyBehavior.NULL;
this.emptyDefaultExpression = null;
return this;
}
@Override
public JpaJsonValueExpression<T> defaultOnEmpty(jakarta.persistence.criteria.Expression<?> expression) {
this.emptyBehavior = EmptyBehavior.DEFAULT;
//noinspection unchecked
this.emptyDefaultExpression = (SqmExpression<T>) expression;
return this;
}
@Override
public Expression convertToSqlAst(SqmToSqlAstConverter walker) {
final @Nullable ReturnableType<?> resultType = resolveResultType( walker );
final List<SqlAstNode> arguments = resolveSqlAstArguments( getArguments(), walker );
final ArgumentsValidator validator = getArgumentsValidator();
if ( validator != null ) {
validator.validateSqlTypes( arguments, getFunctionName() );
}
if ( errorBehavior != null ) {
switch ( errorBehavior ) {
case NULL:
arguments.add( JsonValueErrorBehavior.NULL );
break;
case ERROR:
arguments.add( JsonValueErrorBehavior.ERROR );
break;
case DEFAULT:
arguments.add( JsonValueErrorBehavior.defaultOnError(
(Expression) errorDefaultExpression.accept( walker )
) );
break;
}
}
if ( emptyBehavior != null ) {
switch ( emptyBehavior ) {
case NULL:
arguments.add( JsonValueEmptyBehavior.NULL );
break;
case ERROR:
arguments.add( JsonValueEmptyBehavior.ERROR );
break;
case DEFAULT:
arguments.add( JsonValueEmptyBehavior.defaultOnEmpty(
(Expression) emptyDefaultExpression.accept( walker )
) );
break;
}
}
return new SelfRenderingFunctionSqlAstExpression(
getFunctionName(),
getFunctionRenderer(),
arguments,
resultType,
resultType == null ? null : getMappingModelExpressible( walker, resultType, arguments )
);
}
@Override
public void appendHqlString(StringBuilder sb) {
sb.append( "json_value(" );
getArguments().get( 0 ).appendHqlString( sb );
sb.append( ',' );
getArguments().get( 1 ).appendHqlString( sb );
if ( getArguments().size() > 2 ) {
sb.append( " returning " );
getArguments().get( 2 ).appendHqlString( sb );
}
switch ( errorBehavior ) {
case NULL:
sb.append( " null on error" );
break;
case ERROR:
sb.append( " error on error" );
break;
case DEFAULT:
sb.append( " default " );
errorDefaultExpression.appendHqlString( sb );
sb.append( " on error" );
break;
}
switch ( emptyBehavior ) {
case NULL:
sb.append( " null on empty" );
break;
case ERROR:
sb.append( " error on empty" );
break;
case DEFAULT:
sb.append( " default " );
emptyDefaultExpression.appendHqlString( sb );
sb.append( " on empty" );
break;
}
sb.append( ')' );
}
}

View File

@ -10,6 +10,7 @@ import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.internal.util.collections.Stack;
import org.hibernate.query.spi.QueryOptions;
import org.hibernate.sql.ast.tree.SqlAstNode;
import org.hibernate.sql.ast.tree.expression.Expression;
import org.hibernate.sql.ast.tree.select.QueryPart;
import org.hibernate.sql.exec.spi.JdbcOperation;
import org.hibernate.sql.exec.spi.JdbcParameterBindings;
@ -21,6 +22,12 @@ public interface SqlAstTranslator<T extends JdbcOperation> extends SqlAstWalker
SessionFactoryImplementor getSessionFactory();
/**
* Returns the literal value of the given expression, inlining a parameter value if necessary.
* @since 7.0
*/
<X> X getLiteralValue(Expression expression);
/**
* Renders the given SQL AST node with the given rendering mode.
*/

View File

@ -658,6 +658,11 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
this.limitParameter = limitParameter;
}
@Override
public <X> X getLiteralValue(Expression expression) {
return interpretExpression( expression, jdbcParameterBindings );
}
@SuppressWarnings("unchecked")
protected <R> R interpretExpression(Expression expression, JdbcParameterBindings jdbcParameterBindings) {
if ( expression instanceof Literal ) {

View File

@ -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.sql.ast.tree.expression;
import org.hibernate.sql.ast.SqlAstWalker;
import org.hibernate.sql.ast.tree.SqlAstNode;
import org.checkerframework.checker.nullness.qual.Nullable;
/**
* @since 7.0
*/
public class JsonValueEmptyBehavior implements SqlAstNode {
public static final JsonValueEmptyBehavior NULL = new JsonValueEmptyBehavior( null );
public static final JsonValueEmptyBehavior ERROR = new JsonValueEmptyBehavior( null );
private final @Nullable Expression defaultExpression;
private JsonValueEmptyBehavior(@Nullable Expression defaultExpression) {
this.defaultExpression = defaultExpression;
}
public static JsonValueEmptyBehavior defaultOnEmpty(Expression defaultExpression) {
return new JsonValueEmptyBehavior( defaultExpression );
}
public @Nullable Expression getDefaultExpression() {
return defaultExpression;
}
@Override
public void accept(SqlAstWalker sqlTreeWalker) {
throw new UnsupportedOperationException("JsonValueEmptyBehavior doesn't support walking");
}
}

View File

@ -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.sql.ast.tree.expression;
import org.hibernate.sql.ast.SqlAstWalker;
import org.hibernate.sql.ast.tree.SqlAstNode;
import org.checkerframework.checker.nullness.qual.Nullable;
/**
* @since 7.0
*/
public class JsonValueErrorBehavior implements SqlAstNode {
public static final JsonValueErrorBehavior NULL = new JsonValueErrorBehavior( null );
public static final JsonValueErrorBehavior ERROR = new JsonValueErrorBehavior( null );
private final @Nullable Expression defaultExpression;
private JsonValueErrorBehavior(@Nullable Expression defaultExpression) {
this.defaultExpression = defaultExpression;
}
public static JsonValueErrorBehavior defaultOnError(Expression defaultExpression) {
return new JsonValueErrorBehavior( defaultExpression );
}
public @Nullable Expression getDefaultExpression() {
return defaultExpression;
}
@Override
public void accept(SqlAstWalker sqlTreeWalker) {
throw new UnsupportedOperationException("JsonValueErrorBehavior doesn't support walking");
}
}

View File

@ -989,4 +989,36 @@ public class SqlTypes {
return false;
}
}
/**
* Does the typecode represent a JSON type.
*
* @param typeCode - a JDBC type code
* @since 7.0
*/
public static boolean isJsonType(int typeCode) {
switch ( typeCode ) {
case JSON:
case JSON_ARRAY:
return true;
default:
return false;
}
}
/**
* Does the typecode represent a JSON type or a type that can be implicitly cast to JSON.
*
* @param typeCode - a JDBC type code
* @since 7.0
*/
public static boolean isImplicitJsonType(int typeCode) {
switch ( typeCode ) {
case JSON:
case JSON_ARRAY:
return true;
default:
return isCharacterOrClobType( typeCode );
}
}
}

View File

@ -425,6 +425,16 @@ public interface JdbcType extends Serializable {
return isSpatialType( getDefaultSqlTypeCode() );
}
@Incubating
default boolean isJson() {
return isJsonType( getDefaultSqlTypeCode() );
}
@Incubating
default boolean isImplicitJson() {
return isImplicitJsonType( getDefaultSqlTypeCode() );
}
@Incubating
default boolean isBoolean() {
return getDefaultSqlTypeCode() == BOOLEAN;

View File

@ -350,6 +350,8 @@ public class TypeConfiguration implements SessionFactoryObserver, Serializable {
case "truefalse": return basicTypeRegistry.getRegisteredType( StandardBasicTypes.TRUE_FALSE.getName() );
case "yesno": return basicTypeRegistry.getRegisteredType( StandardBasicTypes.YES_NO.getName() );
case "numericboolean": return basicTypeRegistry.getRegisteredType( StandardBasicTypes.NUMERIC_BOOLEAN.getName() );
case "json": return basicTypeRegistry.resolve( Object.class, SqlTypes.JSON );
case "xml": return basicTypeRegistry.resolve( Object.class, SqlTypes.SQLXML );
//really not sure about this one - it works well for casting from binary
//to UUID, but people will want to use it to cast from varchar, and that
//won't work at all without some special casing in the Dialects

View File

@ -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.orm.test.function.json;
import java.util.HashMap;
import java.util.Map;
import org.hibernate.annotations.JdbcTypeCode;
import org.hibernate.type.SqlTypes;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
@Entity
public class EntityWithJson {
@Id
private Long id;
@JdbcTypeCode(SqlTypes.JSON)
private Map<String, Object> json = new HashMap<>();;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Map<String, Object> getJson() {
return json;
}
public void setJson(Map<String, Object> json) {
this.json = json;
}
}

View File

@ -0,0 +1,123 @@
/*
* 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 java.util.HashMap;
import java.util.List;
import org.hibernate.HibernateException;
import org.hibernate.JDBCException;
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.SessionFactory;
import org.hibernate.testing.orm.junit.SessionFactoryScope;
import org.hibernate.testing.orm.junit.SkipForDialect;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import jakarta.persistence.Tuple;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.fail;
/**
* @author Christian Beikov
*/
@DomainModel(annotatedClasses = EntityWithJson.class)
@SessionFactory
@RequiresDialectFeature( feature = DialectFeatureChecks.SupportsJsonValue.class)
public class JsonValueTest {
@BeforeEach
public void prepareData(SessionFactoryScope scope) {
scope.inTransaction( em -> {
EntityWithJson entity = new EntityWithJson();
entity.setId( 1L );
entity.getJson().put( "theInt", 1 );
entity.getJson().put( "theFloat", 0.1 );
entity.getJson().put( "theString", "abc" );
entity.getJson().put( "theBoolean", true );
entity.getJson().put( "theNull", null );
entity.getJson().put( "theArray", new String[] { "a", "b", "c" } );
entity.getJson().put( "theObject", new HashMap<>( entity.getJson() ) );
em.persist(entity);
} );
}
@AfterEach
public void cleanup(SessionFactoryScope scope) {
scope.inTransaction( em -> {
em.createMutationQuery( "delete from EntityWithJson" ).executeUpdate();
} );
}
@Test
public void testSimple(SessionFactoryScope scope) {
scope.inSession( em -> {
//tag::hql-json-value-example[]
List<Tuple> results = em.createQuery( "select json_value(e.json, '$.theString') from EntityWithJson e", Tuple.class )
.getResultList();
//end::hql-json-value-example[]
assertEquals( 1, results.size() );
} );
}
@Test
public void testReturning(SessionFactoryScope scope) {
scope.inSession( em -> {
//tag::hql-json-value-returning-example[]
List<Tuple> results = em.createQuery( "select json_value(e.json, '$.theInt' returning Integer) from EntityWithJson e", Tuple.class )
.getResultList();
//end::hql-json-value-returning-example[]
assertEquals( 1, results.size() );
} );
}
@Test
@SkipForDialect(dialectClass = MariaDBDialect.class, reason = "MariaDB reports the error 4038 as warning and simply returns null")
public void testOnError(SessionFactoryScope scope) {
scope.inSession( em -> {
try {
//tag::hql-json-value-on-error-example[]
em.createQuery( "select json_value('invalidJson', '$.theInt' error on error) from EntityWithJson e")
.getResultList();
//end::hql-json-value-on-error-example[]
fail("error clause should fail because of invalid json path");
}
catch ( HibernateException e ) {
if ( !( e instanceof JDBCException ) && !( e instanceof ExecutionException ) ) {
throw e;
}
}
} );
}
@Test
@RequiresDialectFeature( feature = DialectFeatureChecks.SupportsJsonValueErrorBehavior.class)
public void testOnEmpty(SessionFactoryScope scope) {
scope.inSession( em -> {
try {
//tag::hql-json-value-on-empty-example[]
em.createQuery("select json_value(e.json, '$.nonExisting' error on empty error on error) from EntityWithJson e" )
.getResultList();
//end::hql-json-value-on-empty-example[]
fail("empty clause should fail because of json path doesn't produce results");
}
catch ( HibernateException e ) {
if ( !( e instanceof JDBCException ) && !( e instanceof ExecutionException ) ) {
throw e;
}
}
} );
}
}

View File

@ -0,0 +1,101 @@
/*
* 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.query.hql;
import java.util.HashMap;
import java.util.Map;
import org.hibernate.annotations.JdbcTypeCode;
import org.hibernate.type.SqlTypes;
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.SessionFactory;
import org.hibernate.testing.orm.junit.SessionFactoryScope;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Tuple;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
@DomainModel( annotatedClasses = JsonFunctionTests.JsonHolder.class)
@SessionFactory
@Jira("https://hibernate.atlassian.net/browse/HHH-18496")
public class JsonFunctionTests {
JsonHolder entity;
@BeforeAll
public void prepareData(SessionFactoryScope scope) {
scope.inTransaction(
em -> {
entity = new JsonHolder();
entity.id = 1L;
entity.json = new HashMap<>();
entity.json.put( "theInt", 1 );
entity.json.put( "theFloat", 0.1 );
entity.json.put( "theString", "abc" );
entity.json.put( "theBoolean", true );
entity.json.put( "theNull", null );
entity.json.put( "theArray", new String[] { "a", "b", "c" } );
entity.json.put( "theObject", new HashMap<>( entity.json ) );
em.persist(entity);
}
);
}
@Test
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsJsonValue.class)
public void testJsonValue(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
Tuple tuple = session.createQuery(
"select " +
"json_value(e.json, '$.theInt'), " +
"json_value(e.json, '$.theFloat'), " +
"json_value(e.json, '$.theString'), " +
"json_value(e.json, '$.theBoolean'), " +
"json_value(e.json, '$.theNull'), " +
"json_value(e.json, '$.theArray'), " +
"json_value(e.json, '$.theArray[1]'), " +
"json_value(e.json, '$.theObject'), " +
"json_value(e.json, '$.theObject.theInt'), " +
"json_value(e.json, '$.theObject.theArray[2]') " +
"from JsonHolder e " +
"where e.id = 1L",
Tuple.class
).getSingleResult();
assertEquals( entity.json.get( "theInt" ).toString(), tuple.get( 0 ) );
assertEquals( entity.json.get( "theFloat" ), Double.parseDouble( tuple.get( 1, String.class ) ) );
assertEquals( entity.json.get( "theString" ), tuple.get( 2 ) );
assertEquals( entity.json.get( "theBoolean" ).toString(), tuple.get( 3 ) );
assertNull( tuple.get( 4 ) );
// PostgreSQL emulation returns non-null value
// assertNull( tuple.get( 5 ) );
assertEquals( ( (String[]) entity.json.get( "theArray" ) )[1], tuple.get( 6 ) );
// PostgreSQL emulation returns non-null value
// assertNull( tuple.get( 7 ) );
assertEquals( entity.json.get( "theInt" ).toString(), tuple.get( 8 ) );
assertEquals( ( (String[]) entity.json.get( "theArray" ) )[2], tuple.get( 9 ) );
}
);
}
@Entity(name = "JsonHolder")
public static class JsonHolder {
@Id
Long id;
@JdbcTypeCode(SqlTypes.JSON)
Map<String, Object> json;
}
}

View File

@ -5,8 +5,52 @@
package org.hibernate.testing.orm.junit;
import java.sql.Types;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import org.hibernate.DuplicateMappingException;
import org.hibernate.MappingException;
import org.hibernate.SessionFactory;
import org.hibernate.annotations.CollectionTypeRegistration;
import org.hibernate.boot.SessionFactoryBuilder;
import org.hibernate.boot.internal.MetadataBuilderImpl;
import org.hibernate.boot.internal.NamedProcedureCallDefinitionImpl;
import org.hibernate.boot.model.FunctionContributions;
import org.hibernate.boot.model.IdentifierGeneratorDefinition;
import org.hibernate.boot.model.NamedEntityGraphDefinition;
import org.hibernate.boot.model.TruthValue;
import org.hibernate.boot.model.TypeContributions;
import org.hibernate.boot.model.TypeDefinition;
import org.hibernate.boot.model.TypeDefinitionRegistry;
import org.hibernate.boot.model.convert.spi.ConverterAutoApplyHandler;
import org.hibernate.boot.model.convert.spi.ConverterDescriptor;
import org.hibernate.boot.model.convert.spi.ConverterRegistry;
import org.hibernate.boot.model.convert.spi.RegisteredConversion;
import org.hibernate.boot.model.internal.AnnotatedClassType;
import org.hibernate.boot.model.naming.Identifier;
import org.hibernate.boot.model.naming.ObjectNameNormalizer;
import org.hibernate.boot.model.relational.AuxiliaryDatabaseObject;
import org.hibernate.boot.model.relational.Database;
import org.hibernate.boot.models.spi.GlobalRegistrations;
import org.hibernate.boot.models.xml.spi.PersistenceUnitMetadata;
import org.hibernate.boot.query.NamedHqlQueryDefinition;
import org.hibernate.boot.query.NamedNativeQueryDefinition;
import org.hibernate.boot.query.NamedProcedureCallDefinition;
import org.hibernate.boot.query.NamedResultSetMappingDescriptor;
import org.hibernate.boot.spi.BootstrapContext;
import org.hibernate.boot.spi.EffectiveMappingDefaults;
import org.hibernate.boot.spi.InFlightMetadataCollector;
import org.hibernate.boot.spi.MetadataBuildingContext;
import org.hibernate.boot.spi.MetadataBuildingOptions;
import org.hibernate.boot.spi.NaturalIdUniqueKeyBinder;
import org.hibernate.boot.spi.PropertyData;
import org.hibernate.boot.spi.SecondPass;
import org.hibernate.community.dialect.FirebirdDialect;
import org.hibernate.dialect.HANADialect;
import org.hibernate.dialect.CockroachDialect;
@ -27,11 +71,39 @@ import org.hibernate.dialect.SybaseDialect;
import org.hibernate.dialect.SybaseDriverKind;
import org.hibernate.dialect.TiDBDialect;
import org.hibernate.dialect.TimeZoneSupport;
import org.hibernate.engine.spi.FilterDefinition;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.mapping.AggregateColumn;
import org.hibernate.mapping.Collection;
import org.hibernate.mapping.Column;
import org.hibernate.mapping.Component;
import org.hibernate.mapping.FetchProfile;
import org.hibernate.mapping.Join;
import org.hibernate.mapping.MappedSuperclass;
import org.hibernate.mapping.PersistentClass;
import org.hibernate.mapping.Table;
import org.hibernate.metamodel.CollectionClassification;
import org.hibernate.metamodel.mapping.DiscriminatorType;
import org.hibernate.metamodel.spi.EmbeddableInstantiator;
import org.hibernate.models.spi.ClassDetails;
import org.hibernate.models.spi.SourceModelBuildingContext;
import org.hibernate.query.named.NamedObjectRepository;
import org.hibernate.query.sqm.FetchClauseType;
import org.hibernate.query.sqm.function.SqmFunctionDescriptor;
import org.hibernate.query.sqm.function.SqmFunctionRegistry;
import org.hibernate.service.ServiceRegistry;
import org.hibernate.sql.ast.spi.StringBuilderSqlAppender;
import org.hibernate.type.SqlTypes;
import org.hibernate.type.Type;
import org.hibernate.type.descriptor.java.JavaType;
import org.hibernate.type.descriptor.jdbc.JdbcType;
import org.hibernate.type.spi.TypeConfiguration;
import org.hibernate.usertype.CompositeUserType;
import org.hibernate.usertype.UserType;
import org.hibernate.testing.boot.BootstrapContextImpl;
import jakarta.persistence.AttributeConverter;
/**
* Container class for different implementation of the {@link DialectFeatureCheck} interface.
@ -654,6 +726,26 @@ abstract public class DialectFeatureChecks {
}
}
public static class SupportsJsonValue implements DialectFeatureCheck {
public boolean apply(Dialect dialect) {
return definesFunction( dialect, "json_value" );
}
}
public static class SupportsJsonValueErrorBehavior implements DialectFeatureCheck {
public boolean apply(Dialect dialect) {
return definesFunction( dialect, "json_value" )
// H2 emulation doesn't support error behavior
&& !( dialect instanceof H2Dialect )
// MariaDB simply doesn't support the on error and on empty clauses
&& !( dialect instanceof MariaDBDialect )
// Cockroach doesn't have a native json_value function
&& !( dialect instanceof CockroachDialect )
// PostgreSQL added support for native json_value in version 17
&& ( !( dialect instanceof PostgreSQLDialect ) || dialect.getVersion().isSameOrAfter( 17 ) );
}
}
public static class IsJtds implements DialectFeatureCheck {
public boolean apply(Dialect dialect) {
return dialect instanceof SybaseDialect && ( (SybaseDialect) dialect ).getDriverKind() == SybaseDriverKind.JTDS;
@ -697,4 +789,772 @@ abstract public class DialectFeatureChecks {
return dialect.getNationalizationSupport() == NationalizationSupport.EXPLICIT;
}
}
private static final HashMap<Dialect, SqmFunctionRegistry> FUNCTION_REGISTRIES = new HashMap<>();
public static boolean definesFunction(Dialect dialect, String functionName) {
SqmFunctionRegistry sqmFunctionRegistry = FUNCTION_REGISTRIES.get( dialect );
if ( sqmFunctionRegistry == null ) {
final TypeConfiguration typeConfiguration = new TypeConfiguration();
final SqmFunctionRegistry functionRegistry = new SqmFunctionRegistry();
typeConfiguration.scope( new FakeMetadataBuildingContext( typeConfiguration, functionRegistry ) );
final FakeTypeContributions typeContributions = new FakeTypeContributions( typeConfiguration );
final FakeFunctionContributions functionContributions = new FakeFunctionContributions(
dialect,
typeConfiguration,
functionRegistry
);
dialect.contribute( typeContributions, typeConfiguration.getServiceRegistry() );
dialect.initializeFunctionRegistry( functionContributions );
FUNCTION_REGISTRIES.put( dialect, sqmFunctionRegistry = functionContributions.functionRegistry );
}
return sqmFunctionRegistry.findFunctionDescriptor( functionName ) != null;
}
private static class FakeTypeContributions implements TypeContributions {
private final TypeConfiguration typeConfiguration;
public FakeTypeContributions(TypeConfiguration typeConfiguration) {
this.typeConfiguration = typeConfiguration;
}
@Override
public TypeConfiguration getTypeConfiguration() {
return typeConfiguration;
}
}
private static class FakeFunctionContributions implements FunctionContributions {
private final Dialect dialect;
private final TypeConfiguration typeConfiguration;
private final SqmFunctionRegistry functionRegistry;
public FakeFunctionContributions(Dialect dialect, TypeConfiguration typeConfiguration, SqmFunctionRegistry functionRegistry) {
this.dialect = dialect;
this.typeConfiguration = typeConfiguration;
this.functionRegistry = functionRegistry;
}
@Override
public Dialect getDialect() {
return dialect;
}
@Override
public TypeConfiguration getTypeConfiguration() {
return typeConfiguration;
}
@Override
public SqmFunctionRegistry getFunctionRegistry() {
return functionRegistry;
}
@Override
public ServiceRegistry getServiceRegistry() {
return null;
}
}
public static class FakeMetadataBuildingContext implements MetadataBuildingContext, InFlightMetadataCollector {
private final TypeConfiguration typeConfiguration;
private final SqmFunctionRegistry functionRegistry;
private final MetadataBuilderImpl.MetadataBuildingOptionsImpl options;
private final BootstrapContextImpl bootstrapContext;
private final Database database;
public FakeMetadataBuildingContext(TypeConfiguration typeConfiguration, SqmFunctionRegistry functionRegistry) {
this.typeConfiguration = typeConfiguration;
this.functionRegistry = functionRegistry;
this.bootstrapContext = new BootstrapContextImpl();
this.options = new MetadataBuilderImpl.MetadataBuildingOptionsImpl( bootstrapContext.getServiceRegistry() );
this.options.setBootstrapContext( bootstrapContext );
this.database = new Database( options, null );
}
@Override
public BootstrapContext getBootstrapContext() {
return bootstrapContext;
}
@Override
public MetadataBuildingOptions getBuildingOptions() {
return options;
}
@Override
public Database getDatabase() {
return database;
}
@Override
public MetadataBuildingOptions getMetadataBuildingOptions() {
return options;
}
@Override
public TypeConfiguration getTypeConfiguration() {
return typeConfiguration;
}
@Override
public SqmFunctionRegistry getFunctionRegistry() {
return functionRegistry;
}
// The rest are no-ops
@Override
public EffectiveMappingDefaults getEffectiveDefaults() {
return null;
}
@Override
public InFlightMetadataCollector getMetadataCollector() {
return this;
}
@Override
public ObjectNameNormalizer getObjectNameNormalizer() {
return null;
}
@Override
public TypeDefinitionRegistry getTypeDefinitionRegistry() {
return null;
}
@Override
public String getCurrentContributorName() {
return "";
}
@Override
public SourceModelBuildingContext getSourceModelBuildingContext() {
return null;
}
@Override
public GlobalRegistrations getGlobalRegistrations() {
return null;
}
@Override
public PersistenceUnitMetadata getPersistenceUnitMetadata() {
return null;
}
@Override
public void addEntityBinding(PersistentClass persistentClass) throws DuplicateMappingException {
}
@Override
public Map<String, PersistentClass> getEntityBindingMap() {
return Map.of();
}
@Override
public void registerComponent(Component component) {
}
@Override
public void registerGenericComponent(Component component) {
}
@Override
public void registerEmbeddableSubclass(ClassDetails superclass, ClassDetails subclass) {
}
@Override
public List<ClassDetails> getEmbeddableSubclasses(ClassDetails superclass) {
return List.of();
}
@Override
public void addImport(String importName, String className) throws DuplicateMappingException {
}
@Override
public void addCollectionBinding(Collection collection) throws DuplicateMappingException {
}
@Override
public Table addTable(
String schema,
String catalog,
String name,
String subselect,
boolean isAbstract,
MetadataBuildingContext buildingContext) {
return null;
}
@Override
public Table addDenormalizedTable(
String schema,
String catalog,
String name,
boolean isAbstract,
String subselect,
Table includedTable,
MetadataBuildingContext buildingContext) throws DuplicateMappingException {
return null;
}
@Override
public void addNamedQuery(NamedHqlQueryDefinition<?> query) throws DuplicateMappingException {
}
@Override
public void addNamedNativeQuery(NamedNativeQueryDefinition<?> query) throws DuplicateMappingException {
}
@Override
public void addResultSetMapping(NamedResultSetMappingDescriptor resultSetMappingDefinition)
throws DuplicateMappingException {
}
@Override
public void addNamedProcedureCallDefinition(NamedProcedureCallDefinition definition)
throws DuplicateMappingException {
}
@Override
public void addNamedEntityGraph(NamedEntityGraphDefinition namedEntityGraphDefinition) {
}
@Override
public void addTypeDefinition(TypeDefinition typeDefinition) {
}
@Override
public void addFilterDefinition(FilterDefinition definition) {
}
@Override
public void addAuxiliaryDatabaseObject(AuxiliaryDatabaseObject auxiliaryDatabaseObject) {
}
@Override
public void addFetchProfile(FetchProfile profile) {
}
@Override
public void addIdentifierGenerator(IdentifierGeneratorDefinition generatorDefinition) {
}
@Override
public ConverterRegistry getConverterRegistry() {
return null;
}
@Override
public void addAttributeConverter(ConverterDescriptor descriptor) {
}
@Override
public void addAttributeConverter(Class<? extends AttributeConverter<?, ?>> converterClass) {
}
@Override
public void addRegisteredConversion(RegisteredConversion conversion) {
}
@Override
public ConverterAutoApplyHandler getAttributeConverterAutoApplyHandler() {
return null;
}
@Override
public void addSecondPass(SecondPass secondPass) {
}
@Override
public void addSecondPass(SecondPass sp, boolean onTopOfTheQueue) {
}
@Override
public void addTableNameBinding(Identifier logicalName, Table table) {
}
@Override
public void addTableNameBinding(
String schema,
String catalog,
String logicalName,
String realTableName,
Table denormalizedSuperTable) {
}
@Override
public String getLogicalTableName(Table ownerTable) {
return "";
}
@Override
public String getPhysicalTableName(Identifier logicalName) {
return "";
}
@Override
public String getPhysicalTableName(String logicalName) {
return "";
}
@Override
public void addColumnNameBinding(Table table, Identifier logicalColumnName, Column column) {
}
@Override
public void addColumnNameBinding(Table table, String logicalColumnName, Column column) {
}
@Override
public String getPhysicalColumnName(Table table, Identifier logicalName) throws MappingException {
return "";
}
@Override
public String getPhysicalColumnName(Table table, String logicalName) throws MappingException {
return "";
}
@Override
public String getLogicalColumnName(Table table, Identifier physicalName) {
return "";
}
@Override
public String getLogicalColumnName(Table table, String physicalName) {
return "";
}
@Override
public void addDefaultIdentifierGenerator(IdentifierGeneratorDefinition generatorDefinition) {
}
@Override
public void addDefaultQuery(NamedHqlQueryDefinition<?> queryDefinition) {
}
@Override
public void addDefaultNamedNativeQuery(NamedNativeQueryDefinition<?> query) {
}
@Override
public void addDefaultResultSetMapping(NamedResultSetMappingDescriptor definition) {
}
@Override
public void addDefaultNamedProcedureCall(NamedProcedureCallDefinitionImpl procedureCallDefinition) {
}
@Override
public AnnotatedClassType addClassType(ClassDetails classDetails) {
return null;
}
@Override
public AnnotatedClassType getClassType(ClassDetails classDetails) {
return null;
}
@Override
public void addMappedSuperclass(Class<?> type, MappedSuperclass mappedSuperclass) {
}
@Override
public MappedSuperclass getMappedSuperclass(Class<?> type) {
return null;
}
@Override
public PropertyData getPropertyAnnotatedWithMapsId(ClassDetails persistentClassDetails, String propertyName) {
return null;
}
@Override
public void addPropertyAnnotatedWithMapsId(
ClassDetails entityClassDetails,
PropertyData propertyAnnotatedElement) {
}
@Override
public void addPropertyAnnotatedWithMapsIdSpecj(
ClassDetails entityClassDetails,
PropertyData specJPropertyData,
String s) {
}
@Override
public void addToOneAndIdProperty(ClassDetails entityClassDetails, PropertyData propertyAnnotatedElement) {
}
@Override
public PropertyData getPropertyAnnotatedWithIdAndToOne(
ClassDetails persistentClassDetails,
String propertyName) {
return null;
}
@Override
public boolean isInSecondPass() {
return false;
}
@Override
public NaturalIdUniqueKeyBinder locateNaturalIdUniqueKeyBinder(String entityName) {
return null;
}
@Override
public void registerNaturalIdUniqueKeyBinder(String entityName, NaturalIdUniqueKeyBinder ukBinder) {
}
@Override
public void registerValueMappingResolver(Function<MetadataBuildingContext, Boolean> resolver) {
}
@Override
public void addJavaTypeRegistration(Class<?> javaType, JavaType<?> jtd) {
}
@Override
public void addJdbcTypeRegistration(int typeCode, JdbcType jdbcType) {
}
@Override
public void registerEmbeddableInstantiator(
Class<?> embeddableType,
Class<? extends EmbeddableInstantiator> instantiator) {
}
@Override
public Class<? extends EmbeddableInstantiator> findRegisteredEmbeddableInstantiator(Class<?> embeddableType) {
return null;
}
@Override
public void registerCompositeUserType(Class<?> embeddableType, Class<? extends CompositeUserType<?>> userType) {
}
@Override
public Class<? extends CompositeUserType<?>> findRegisteredCompositeUserType(Class<?> embeddableType) {
return null;
}
@Override
public void registerUserType(Class<?> embeddableType, Class<? extends UserType<?>> userType) {
}
@Override
public Class<? extends UserType<?>> findRegisteredUserType(Class<?> basicType) {
return null;
}
@Override
public void addCollectionTypeRegistration(CollectionTypeRegistration registrationAnnotation) {
}
@Override
public void addCollectionTypeRegistration(
CollectionClassification classification,
CollectionTypeRegistrationDescriptor descriptor) {
}
@Override
public CollectionTypeRegistrationDescriptor findCollectionTypeRegistration(CollectionClassification classification) {
return null;
}
@Override
public void addDelayedPropertyReferenceHandler(DelayedPropertyReferenceHandler handler) {
}
@Override
public void addPropertyReference(String entityName, String propertyName) {
}
@Override
public void addUniquePropertyReference(String entityName, String propertyName) {
}
@Override
public void addPropertyReferencedAssociation(String s, String propertyName, String syntheticPropertyName) {
}
@Override
public String getPropertyReferencedAssociation(String entityName, String mappedBy) {
return "";
}
@Override
public void addMappedBy(String name, String mappedBy, String propertyName) {
}
@Override
public String getFromMappedBy(String ownerEntityName, String propertyName) {
return "";
}
@Override
public EntityTableXref getEntityTableXref(String entityName) {
return null;
}
@Override
public EntityTableXref addEntityTableXref(
String entityName,
Identifier primaryTableLogicalName,
Table primaryTable,
EntityTableXref superEntityTableXref) {
return null;
}
@Override
public Map<String, Join> getJoins(String entityName) {
return Map.of();
}
@Override
public SessionFactoryBuilder getSessionFactoryBuilder() {
return null;
}
@Override
public SessionFactory buildSessionFactory() {
return null;
}
@Override
public UUID getUUID() {
return null;
}
@Override
public java.util.Collection<PersistentClass> getEntityBindings() {
return List.of();
}
@Override
public PersistentClass getEntityBinding(String entityName) {
return null;
}
@Override
public java.util.Collection<Collection> getCollectionBindings() {
return List.of();
}
@Override
public Collection getCollectionBinding(String role) {
return null;
}
@Override
public Map<String, String> getImports() {
return Map.of();
}
@Override
public NamedHqlQueryDefinition<?> getNamedHqlQueryMapping(String name) {
return null;
}
@Override
public void visitNamedHqlQueryDefinitions(Consumer<NamedHqlQueryDefinition<?>> definitionConsumer) {
}
@Override
public NamedNativeQueryDefinition<?> getNamedNativeQueryMapping(String name) {
return null;
}
@Override
public void visitNamedNativeQueryDefinitions(Consumer<NamedNativeQueryDefinition<?>> definitionConsumer) {
}
@Override
public NamedProcedureCallDefinition getNamedProcedureCallMapping(String name) {
return null;
}
@Override
public void visitNamedProcedureCallDefinition(Consumer<NamedProcedureCallDefinition> definitionConsumer) {
}
@Override
public NamedResultSetMappingDescriptor getResultSetMapping(String name) {
return null;
}
@Override
public void visitNamedResultSetMappingDefinition(Consumer<NamedResultSetMappingDescriptor> definitionConsumer) {
}
@Override
public TypeDefinition getTypeDefinition(String typeName) {
return null;
}
@Override
public Map<String, FilterDefinition> getFilterDefinitions() {
return Map.of();
}
@Override
public FilterDefinition getFilterDefinition(String name) {
return null;
}
@Override
public FetchProfile getFetchProfile(String name) {
return null;
}
@Override
public java.util.Collection<FetchProfile> getFetchProfiles() {
return List.of();
}
@Override
public NamedEntityGraphDefinition getNamedEntityGraph(String name) {
return null;
}
@Override
public Map<String, NamedEntityGraphDefinition> getNamedEntityGraphs() {
return Map.of();
}
@Override
public IdentifierGeneratorDefinition getIdentifierGenerator(String name) {
return null;
}
@Override
public java.util.Collection<Table> collectTableMappings() {
return List.of();
}
@Override
public Map<String, SqmFunctionDescriptor> getSqlFunctionMap() {
return Map.of();
}
@Override
public Set<String> getContributors() {
return Set.of();
}
@Override
public NamedObjectRepository buildNamedQueryRepository(SessionFactoryImplementor sessionFactory) {
return null;
}
@Override
public void orderColumns(boolean forceOrdering) {
}
@Override
public void validate() throws MappingException {
}
@Override
public Set<MappedSuperclass> getMappedSuperclassMappingsCopy() {
return Set.of();
}
@Override
public void initSessionFactory(SessionFactoryImplementor sessionFactoryImplementor) {
}
@Override
public void visitRegisteredComponents(Consumer<Component> consumer) {
}
@Override
public Component getGenericComponent(Class<?> componentClass) {
return null;
}
@Override
public DiscriminatorType<?> resolveEmbeddableDiscriminatorType(
Class<?> embeddableClass,
Supplier<DiscriminatorType<?>> supplier) {
return null;
}
@Override
public Type getIdentifierType(String className) throws MappingException {
return null;
}
@Override
public String getIdentifierPropertyName(String className) throws MappingException {
return "";
}
@Override
public Type getReferencedPropertyType(String className, String propertyName) throws MappingException {
return null;
}
}
}