HHH-18496 Add json_value function
This commit is contained in:
parent
ff57a6ced0
commit
d5a3f041b3
|
@ -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
|
||||
|
||||
|
|
|
@ -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";
|
|
@ -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 )
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 ()" )
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)" );
|
||||
}
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 )
|
||||
|
|
|
@ -415,6 +415,10 @@ public class DB2Dialect extends Dialect {
|
|||
|
||||
functionFactory.windowFunctions();
|
||||
functionFactory.listagg( null );
|
||||
|
||||
if ( getDB2Version().isSameOrAfter( 11 ) ) {
|
||||
functionFactory.jsonValue();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 ) )
|
||||
|
|
|
@ -632,6 +632,8 @@ public class MySQLDialect extends Dialect {
|
|||
}
|
||||
|
||||
functionFactory.listagg_groupConcat();
|
||||
|
||||
functionFactory.jsonValue_mysql();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -386,6 +386,8 @@ public class OracleDialect extends Dialect {
|
|||
functionFactory.arrayTrim_oracle();
|
||||
functionFactory.arrayFill_oracle();
|
||||
functionFactory.arrayToString_oracle();
|
||||
|
||||
functionFactory.jsonValue_literal_path();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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)" );
|
||||
}
|
||||
|
|
|
@ -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 ) );
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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( ')' );
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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( '"' );
|
||||
}
|
||||
}
|
|
@ -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 {}
|
||||
}
|
|
@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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( ')' );
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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( ')' );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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( ')' );
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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( "))" );
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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 );
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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?
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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
|
||||
*/
|
||||
|
|
|
@ -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( ')' );
|
||||
}
|
||||
}
|
|
@ -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.
|
||||
*/
|
||||
|
|
|
@ -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 ) {
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
|
||||
}
|
|
@ -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");
|
||||
}
|
||||
|
||||
}
|
|
@ -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 );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
} );
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue