HHH-18496 Add json_query
This commit is contained in:
parent
6454aaf055
commit
6b4cc28f0e
|
@ -1633,6 +1633,7 @@ The following functions deal with SQL JSON types, which are not supported on eve
|
|||
| `json_array()` | Constructs a JSON array from arguments
|
||||
| `json_value()` | Extracts a value from a JSON document by JSON path
|
||||
| `json_exists()` | Checks if a JSON path exists in a JSON document
|
||||
| `json_query()` | Queries non-scalar values by JSON path in a JSON document
|
||||
|===
|
||||
|
||||
|
||||
|
@ -1712,7 +1713,7 @@ 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.
|
||||
|
||||
WARNING: Some databases might also return non-scalar values. Beware that this behavior is not portable.
|
||||
WARNING: Some databases might also allow extracting non-scalar values. Beware that this behavior is not portable.
|
||||
|
||||
NOTE: It is recommended to only us the dot notation for JSON paths instead of the bracket notation,
|
||||
since most databases support only that.
|
||||
|
@ -1832,6 +1833,92 @@ include::{json-example-dir-hql}/JsonExistsTest.java[tags=hql-json-exists-on-erro
|
|||
|
||||
NOTE: The H2 emulation only supports absolute JSON paths using the dot notation.
|
||||
|
||||
[[hql-json-query-function]]
|
||||
===== `json_query()`
|
||||
|
||||
Queries non-scalar values from a JSON document by a https://www.ietf.org/archive/id/draft-goessner-dispatch-jsonpath-00.html[JSON path].
|
||||
|
||||
[[hql-json-query-bnf]]
|
||||
[source, antlrv4, indent=0]
|
||||
----
|
||||
include::{extrasdir}/json_query_bnf.txt[]
|
||||
----
|
||||
|
||||
The first argument is an expression to a JSON document. The second argument is a JSON path as String expression.
|
||||
|
||||
WARNING: Some databases might also allow querying scalar values. Beware that this behavior is not portable.
|
||||
|
||||
NOTE: It is recommended to only us the dot notation for JSON paths instead of the bracket notation,
|
||||
since most databases support only that.
|
||||
|
||||
[[hql-json-query-example]]
|
||||
====
|
||||
[source, java, indent=0]
|
||||
----
|
||||
include::{json-example-dir-hql}/JsonQueryTest.java[tags=hql-json-query-example]
|
||||
----
|
||||
====
|
||||
|
||||
The `passing` clause allows to reuse the same JSON path but pass different values for evaluation.
|
||||
|
||||
[[hql-json-query-passing-example]]
|
||||
====
|
||||
[source, java, indent=0]
|
||||
----
|
||||
include::{json-example-dir-hql}/JsonQueryTest.java[tags=hql-json-query-passing-example]
|
||||
----
|
||||
====
|
||||
|
||||
The `wrapper` clause allows to specify whether results of a query should be wrapped in brackets `[]` i.e. an array.
|
||||
The default behavior is to omit an array wrapper i.e. `without wrapper`.
|
||||
It is an error when a `json_query` returns more than a single result and `without wrapper` is used.
|
||||
How an error like this should be handled can be controlled with the `on error` clause.
|
||||
|
||||
WARNING: Since the default behavior of `on error` is database dependent,
|
||||
some databases might return a comma separated list of values even when using `without wrapper`. This is not portable.
|
||||
|
||||
[[hql-json-query-wrapper-example]]
|
||||
====
|
||||
[source, java, indent=0]
|
||||
----
|
||||
include::{json-example-dir-hql}/JsonQueryTest.java[tags=hql-json-query-with-wrapper-example]
|
||||
----
|
||||
====
|
||||
|
||||
The `on error` clause defines the behavior when an error occurs while querying with 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
|
||||
* Multiple `json_query` results when `without wrapper` is used
|
||||
|
||||
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-query-on-error-example]]
|
||||
====
|
||||
[source, java, indent=0]
|
||||
----
|
||||
include::{json-example-dir-hql}/JsonQueryTest.java[tags=hql-json-query-on-error-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-query-on-empty-example]]
|
||||
====
|
||||
[source, java, indent=0]
|
||||
----
|
||||
include::{json-example-dir-hql}/JsonQueryTest.java[tags=hql-json-query-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
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
"json_exists(" expression, expression passingClause? onErrorClause? ")"
|
||||
"json_exists(" expression "," expression passingClause? onErrorClause? ")"
|
||||
|
||||
passingClause
|
||||
: "passing" expression "as" identifier ("," expression "as" identifier)*
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
"json_query(" expression "," expression passingClause? wrapperClause? onErrorClause? onEmptyClause? ")"
|
||||
|
||||
wrapperClause
|
||||
: "with" ("conditional"|"unconditional")? "array"? "wrapper"
|
||||
| "without" "array"? "wrapper"
|
||||
|
||||
passingClause
|
||||
: "passing" expression "as" identifier ("," expression "as" identifier)*
|
||||
|
||||
onErrorClause
|
||||
: ( "error" | "null" | ( "empty" ( "array" | "object" )? ) ) "on error";
|
||||
|
||||
onEmptyClause
|
||||
: ( "error" | "null" | ( "empty" ( "array" | "object" )? ) ) "on empty";
|
|
@ -1,4 +1,4 @@
|
|||
"json_value(" expression, expression passingClause? ("returning" castTarget)? onErrorClause? onEmptyClause? ")"
|
||||
"json_value(" expression "," expression passingClause? ("returning" castTarget)? onErrorClause? onEmptyClause? ")"
|
||||
|
||||
passingClause
|
||||
: "passing" expression "as" identifier ("," expression "as" identifier)*
|
||||
|
|
|
@ -432,6 +432,7 @@ public class DB2LegacyDialect extends Dialect {
|
|||
|
||||
if ( getDB2Version().isSameOrAfter( 11 ) ) {
|
||||
functionFactory.jsonValue_no_passing();
|
||||
functionFactory.jsonQuery_no_passing();
|
||||
functionFactory.jsonExists_no_passing();
|
||||
functionFactory.jsonObject_db2();
|
||||
functionFactory.jsonArray_db2();
|
||||
|
|
|
@ -404,6 +404,7 @@ public class H2LegacyDialect extends Dialect {
|
|||
|
||||
if ( getVersion().isSameOrAfter( 2, 2, 220 ) ) {
|
||||
functionFactory.jsonValue_h2();
|
||||
functionFactory.jsonQuery_h2();
|
||||
functionFactory.jsonExists_h2();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -654,6 +654,7 @@ public class MySQLLegacyDialect extends Dialect {
|
|||
|
||||
if ( getMySQLVersion().isSameOrAfter( 5, 7 ) ) {
|
||||
functionFactory.jsonValue_mysql();
|
||||
functionFactory.jsonQuery_mysql();
|
||||
functionFactory.jsonExists_mysql();
|
||||
functionFactory.jsonObject_mysql();
|
||||
functionFactory.jsonArray_mysql();
|
||||
|
|
|
@ -323,6 +323,7 @@ public class OracleLegacyDialect extends Dialect {
|
|||
|
||||
if ( getVersion().isSameOrAfter( 12 ) ) {
|
||||
functionFactory.jsonValue_oracle();
|
||||
functionFactory.jsonQuery_oracle();
|
||||
functionFactory.jsonExists_oracle();
|
||||
functionFactory.jsonObject_oracle();
|
||||
functionFactory.jsonArray_oracle();
|
||||
|
|
|
@ -634,12 +634,14 @@ public class PostgreSQLLegacyDialect extends Dialect {
|
|||
|
||||
if ( getVersion().isSameOrAfter( 17 ) ) {
|
||||
functionFactory.jsonValue();
|
||||
functionFactory.jsonQuery();
|
||||
functionFactory.jsonExists();
|
||||
functionFactory.jsonObject();
|
||||
functionFactory.jsonArray();
|
||||
}
|
||||
else {
|
||||
functionFactory.jsonValue_postgresql();
|
||||
functionFactory.jsonQuery_postgresql();
|
||||
functionFactory.jsonExists_postgresql();
|
||||
if ( getVersion().isSameOrAfter( 16 ) ) {
|
||||
functionFactory.jsonObject();
|
||||
|
|
|
@ -402,6 +402,7 @@ public class SQLServerLegacyDialect extends AbstractTransactSQLDialect {
|
|||
functionFactory.hypotheticalOrderedSetAggregates_windowEmulation();
|
||||
if ( getVersion().isSameOrAfter( 13 ) ) {
|
||||
functionFactory.jsonValue_sqlserver();
|
||||
functionFactory.jsonQuery_sqlserver();
|
||||
functionFactory.jsonExists_sqlserver();
|
||||
functionFactory.jsonObject_sqlserver();
|
||||
functionFactory.jsonArray_sqlserver();
|
||||
|
|
|
@ -149,6 +149,7 @@ ABSENT : [aA] [bB] [sS] [eE] [nN] [tT];
|
|||
ALL : [aA] [lL] [lL];
|
||||
AND : [aA] [nN] [dD];
|
||||
ANY : [aA] [nN] [yY];
|
||||
ARRAY : [aA] [rR] [rR] [aA] [yY];
|
||||
AS : [aA] [sS];
|
||||
ASC : [aA] [sS] [cC];
|
||||
AVG : [aA] [vV] [gG];
|
||||
|
@ -160,6 +161,7 @@ CASE : [cC] [aA] [sS] [eE];
|
|||
CAST : [cC] [aA] [sS] [tT];
|
||||
COLLATE : [cC] [oO] [lL] [lL] [aA] [tT] [eE];
|
||||
COLUMN : [cC] [oO] [lL] [uU] [mM] [nN];
|
||||
CONDITIONAL : [cC] [oO] [nN] [dD] [iI] [tT] [iI] [oO] [nN] [aA] [lL];
|
||||
CONFLICT : [cC] [oO] [nN] [fF] [lL] [iI] [cC] [tT];
|
||||
CONSTRAINT : [cC] [oO] [nN] [sS] [tT] [rR] [aA] [iI] [nN] [tT];
|
||||
CONTAINS : [cC] [oO] [nN] [tT] [aA] [iI] [nN] [sS];
|
||||
|
@ -225,6 +227,7 @@ JOIN : [jJ] [oO] [iI] [nN];
|
|||
JSON_ARRAY : [jJ] [sS] [oO] [nN] '_' [aA] [rR] [rR] [aA] [yY];
|
||||
JSON_EXISTS : [jJ] [sS] [oO] [nN] '_' [eE] [xX] [iI] [sS] [tT] [sS];
|
||||
JSON_OBJECT : [jJ] [sS] [oO] [nN] '_' [oO] [bB] [jJ] [eE] [cC] [tT];
|
||||
JSON_QUERY : [jJ] [sS] [oO] [nN] '_' [qQ] [uU] [eE] [rR] [yY];
|
||||
JSON_VALUE : [jJ] [sS] [oO] [nN] '_' [vV] [aA] [lL] [uU] [eE];
|
||||
KEY : [kK] [eE] [yY];
|
||||
KEYS : [kK] [eE] [yY] [sS];
|
||||
|
@ -310,6 +313,7 @@ TRUNC : [tT] [rR] [uU] [nN] [cC];
|
|||
TRUNCATE : [tT] [rR] [uU] [nN] [cC] [aA] [tT] [eE];
|
||||
TYPE : [tT] [yY] [pP] [eE];
|
||||
UNBOUNDED : [uU] [nN] [bB] [oO] [uU] [nN] [dD] [eE] [dD];
|
||||
UNCONDITIONAL : [uU] [nN] [cC] [oO] [nN] [dD] [iI] [tT] [iI] [oO] [nN] [aA] [lL];
|
||||
UNION : [uU] [nN] [iI] [oO] [nN];
|
||||
UPDATE : [uU] [pP] [dD] [aA] [tT] [eE];
|
||||
USING : [uU] [sS] [iI] [nN] [gG];
|
||||
|
@ -321,6 +325,7 @@ WHERE : [wW] [hH] [eE] [rR] [eE];
|
|||
WITH : [wW] [iI] [tT] [hH];
|
||||
WITHIN : [wW] [iI] [tT] [hH] [iI] [nN];
|
||||
WITHOUT : [wW] [iI] [tT] [hH] [oO] [uU] [tT];
|
||||
WRAPPER : [wW] [rR] [aA] [pP] [pP] [eE] [rR];
|
||||
YEAR : [yY] [eE] [aA] [rR];
|
||||
ZONED : [zZ] [oO] [nN] [eE] [dD];
|
||||
|
||||
|
|
|
@ -1625,6 +1625,7 @@ jsonFunction
|
|||
: jsonArrayFunction
|
||||
| jsonExistsFunction
|
||||
| jsonObjectFunction
|
||||
| jsonQueryFunction
|
||||
| jsonValueFunction
|
||||
;
|
||||
|
||||
|
@ -1646,6 +1647,21 @@ jsonValueReturningClause
|
|||
jsonValueOnErrorOrEmptyClause
|
||||
: ( ERROR | NULL | ( DEFAULT expression ) ) ON (ERROR|EMPTY);
|
||||
|
||||
/**
|
||||
* The 'json_query()' function
|
||||
*/
|
||||
jsonQueryFunction
|
||||
: JSON_QUERY LEFT_PAREN expression COMMA expression jsonPassingClause? jsonQueryWrapperClause? jsonQueryOnErrorOrEmptyClause? jsonQueryOnErrorOrEmptyClause? RIGHT_PAREN
|
||||
;
|
||||
|
||||
jsonQueryWrapperClause
|
||||
: WITH (CONDITIONAL|UNCONDITIONAL)? ARRAY? WRAPPER
|
||||
| WITHOUT ARRAY? WRAPPER
|
||||
;
|
||||
|
||||
jsonQueryOnErrorOrEmptyClause
|
||||
: ( ERROR | NULL | ( EMPTY ( ARRAY | OBJECT )? ) ) ON (ERROR|EMPTY);
|
||||
|
||||
/**
|
||||
* The 'json_exists()' function
|
||||
*/
|
||||
|
@ -1699,6 +1715,7 @@ jsonNullClause
|
|||
| ALL
|
||||
| AND
|
||||
| ANY
|
||||
| ARRAY
|
||||
| AS
|
||||
| ASC
|
||||
| AVG
|
||||
|
@ -1710,6 +1727,7 @@ jsonNullClause
|
|||
| CAST
|
||||
| COLLATE
|
||||
| COLUMN
|
||||
| CONDITIONAL
|
||||
| CONFLICT
|
||||
| CONSTRAINT
|
||||
| CONTAINS
|
||||
|
@ -1777,6 +1795,7 @@ jsonNullClause
|
|||
| JSON_ARRAY
|
||||
| JSON_EXISTS
|
||||
| JSON_OBJECT
|
||||
| JSON_QUERY
|
||||
| JSON_VALUE
|
||||
| KEY
|
||||
| KEYS
|
||||
|
@ -1863,6 +1882,7 @@ jsonNullClause
|
|||
| TRUNCATE
|
||||
| TYPE
|
||||
| UNBOUNDED
|
||||
| UNCONDITIONAL
|
||||
| UNION
|
||||
| UPDATE
|
||||
| USING
|
||||
|
@ -1876,6 +1896,7 @@ jsonNullClause
|
|||
| WITH
|
||||
| WITHIN
|
||||
| WITHOUT
|
||||
| WRAPPER
|
||||
| YEAR
|
||||
| ZONED) {
|
||||
logUseOfReservedWordAsIdentifier( getCurrentToken() );
|
||||
|
|
|
@ -418,6 +418,7 @@ public class DB2Dialect extends Dialect {
|
|||
|
||||
if ( getDB2Version().isSameOrAfter( 11 ) ) {
|
||||
functionFactory.jsonValue_no_passing();
|
||||
functionFactory.jsonQuery_no_passing();
|
||||
functionFactory.jsonExists_no_passing();
|
||||
functionFactory.jsonObject_db2();
|
||||
functionFactory.jsonArray_db2();
|
||||
|
|
|
@ -347,6 +347,7 @@ public class H2Dialect extends Dialect {
|
|||
functionFactory.jsonArray();
|
||||
if ( getVersion().isSameOrAfter( 2, 2, 220 ) ) {
|
||||
functionFactory.jsonValue_h2();
|
||||
functionFactory.jsonQuery_h2();
|
||||
functionFactory.jsonExists_h2();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -492,6 +492,7 @@ public class HANADialect extends Dialect {
|
|||
if ( getVersion().isSameOrAfter(2, 0, 20) ) {
|
||||
// Introduced in 2.0 SPS 02
|
||||
functionFactory.jsonValue_no_passing();
|
||||
functionFactory.jsonQuery_no_passing();
|
||||
functionFactory.jsonExists_hana();
|
||||
if ( getVersion().isSameOrAfter(2, 0, 40) ) {
|
||||
// Introduced in 2.0 SPS 04
|
||||
|
|
|
@ -639,6 +639,7 @@ public class MySQLDialect extends Dialect {
|
|||
functionFactory.listagg_groupConcat();
|
||||
|
||||
functionFactory.jsonValue_mysql();
|
||||
functionFactory.jsonQuery_mysql();
|
||||
functionFactory.jsonExists_mysql();
|
||||
functionFactory.jsonObject_mysql();
|
||||
functionFactory.jsonArray_mysql();
|
||||
|
|
|
@ -400,6 +400,7 @@ public class OracleDialect extends Dialect {
|
|||
functionFactory.arrayToString_oracle();
|
||||
|
||||
functionFactory.jsonValue_oracle();
|
||||
functionFactory.jsonQuery_oracle();
|
||||
functionFactory.jsonExists_oracle();
|
||||
functionFactory.jsonObject_oracle();
|
||||
functionFactory.jsonArray_oracle();
|
||||
|
|
|
@ -595,12 +595,14 @@ public class PostgreSQLDialect extends Dialect {
|
|||
|
||||
if ( getVersion().isSameOrAfter( 17 ) ) {
|
||||
functionFactory.jsonValue();
|
||||
functionFactory.jsonQuery();
|
||||
functionFactory.jsonExists();
|
||||
functionFactory.jsonObject();
|
||||
functionFactory.jsonArray();
|
||||
}
|
||||
else {
|
||||
functionFactory.jsonValue_postgresql();
|
||||
functionFactory.jsonQuery_postgresql();
|
||||
functionFactory.jsonExists_postgresql();
|
||||
if ( getVersion().isSameOrAfter( 16 ) ) {
|
||||
functionFactory.jsonObject();
|
||||
|
|
|
@ -420,6 +420,7 @@ public class SQLServerDialect extends AbstractTransactSQLDialect {
|
|||
functionFactory.hypotheticalOrderedSetAggregates_windowEmulation();
|
||||
if ( getVersion().isSameOrAfter( 13 ) ) {
|
||||
functionFactory.jsonValue_sqlserver();
|
||||
functionFactory.jsonQuery_sqlserver();
|
||||
functionFactory.jsonExists_sqlserver();
|
||||
functionFactory.jsonObject_sqlserver();
|
||||
functionFactory.jsonArray_sqlserver();
|
||||
|
|
|
@ -80,6 +80,7 @@ import org.hibernate.dialect.function.json.CockroachDBJsonValueFunction;
|
|||
import org.hibernate.dialect.function.json.DB2JsonArrayFunction;
|
||||
import org.hibernate.dialect.function.json.DB2JsonObjectFunction;
|
||||
import org.hibernate.dialect.function.json.H2JsonExistsFunction;
|
||||
import org.hibernate.dialect.function.json.H2JsonQueryFunction;
|
||||
import org.hibernate.dialect.function.json.H2JsonValueFunction;
|
||||
import org.hibernate.dialect.function.json.HANAJsonArrayFunction;
|
||||
import org.hibernate.dialect.function.json.HANAJsonExistsFunction;
|
||||
|
@ -89,22 +90,26 @@ import org.hibernate.dialect.function.json.HSQLJsonObjectFunction;
|
|||
import org.hibernate.dialect.function.json.JsonArrayFunction;
|
||||
import org.hibernate.dialect.function.json.JsonExistsFunction;
|
||||
import org.hibernate.dialect.function.json.JsonObjectFunction;
|
||||
import org.hibernate.dialect.function.json.JsonQueryFunction;
|
||||
import org.hibernate.dialect.function.json.JsonValueFunction;
|
||||
import org.hibernate.dialect.function.json.MariaDBJsonArrayFunction;
|
||||
import org.hibernate.dialect.function.json.MariaDBJsonValueFunction;
|
||||
import org.hibernate.dialect.function.json.MySQLJsonArrayFunction;
|
||||
import org.hibernate.dialect.function.json.MySQLJsonExistsFunction;
|
||||
import org.hibernate.dialect.function.json.MySQLJsonObjectFunction;
|
||||
import org.hibernate.dialect.function.json.MySQLJsonQueryFunction;
|
||||
import org.hibernate.dialect.function.json.MySQLJsonValueFunction;
|
||||
import org.hibernate.dialect.function.json.OracleJsonArrayFunction;
|
||||
import org.hibernate.dialect.function.json.OracleJsonObjectFunction;
|
||||
import org.hibernate.dialect.function.json.PostgreSQLJsonArrayFunction;
|
||||
import org.hibernate.dialect.function.json.PostgreSQLJsonExistsFunction;
|
||||
import org.hibernate.dialect.function.json.PostgreSQLJsonObjectFunction;
|
||||
import org.hibernate.dialect.function.json.PostgreSQLJsonQueryFunction;
|
||||
import org.hibernate.dialect.function.json.PostgreSQLJsonValueFunction;
|
||||
import org.hibernate.dialect.function.json.SQLServerJsonArrayFunction;
|
||||
import org.hibernate.dialect.function.json.SQLServerJsonExistsFunction;
|
||||
import org.hibernate.dialect.function.json.SQLServerJsonObjectFunction;
|
||||
import org.hibernate.dialect.function.json.SQLServerJsonQueryFunction;
|
||||
import org.hibernate.dialect.function.json.SQLServerJsonValueFunction;
|
||||
import org.hibernate.query.sqm.function.SqmFunctionRegistry;
|
||||
import org.hibernate.query.sqm.produce.function.ArgumentTypesValidator;
|
||||
|
@ -3414,6 +3419,55 @@ public class CommonFunctionFactory {
|
|||
functionRegistry.register( "json_value", new H2JsonValueFunction( typeConfiguration ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* json_query() function
|
||||
*/
|
||||
public void jsonQuery() {
|
||||
functionRegistry.register( "json_query", new JsonQueryFunction( typeConfiguration, true, true ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* json_query() function
|
||||
*/
|
||||
public void jsonQuery_no_passing() {
|
||||
functionRegistry.register( "json_query", new JsonQueryFunction( typeConfiguration, true, false ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Oracle json_query() function
|
||||
*/
|
||||
public void jsonQuery_oracle() {
|
||||
functionRegistry.register( "json_query", new JsonQueryFunction( typeConfiguration, false, false ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* PostgreSQL json_query() function
|
||||
*/
|
||||
public void jsonQuery_postgresql() {
|
||||
functionRegistry.register( "json_query", new PostgreSQLJsonQueryFunction( typeConfiguration ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* MySQL json_query() function
|
||||
*/
|
||||
public void jsonQuery_mysql() {
|
||||
functionRegistry.register( "json_query", new MySQLJsonQueryFunction( typeConfiguration ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* SQL Server json_query() function
|
||||
*/
|
||||
public void jsonQuery_sqlserver() {
|
||||
functionRegistry.register( "json_query", new SQLServerJsonQueryFunction( typeConfiguration ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* H2 json_query() function
|
||||
*/
|
||||
public void jsonQuery_h2() {
|
||||
functionRegistry.register( "json_query", new H2JsonQueryFunction( typeConfiguration ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* json_exists() function
|
||||
*/
|
||||
|
|
|
@ -0,0 +1,67 @@
|
|||
/*
|
||||
* 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.JsonQueryEmptyBehavior;
|
||||
import org.hibernate.sql.ast.tree.expression.JsonQueryErrorBehavior;
|
||||
import org.hibernate.sql.ast.tree.expression.JsonQueryWrapMode;
|
||||
import org.hibernate.type.spi.TypeConfiguration;
|
||||
|
||||
/**
|
||||
* H2 json_query function.
|
||||
*/
|
||||
public class H2JsonQueryFunction extends JsonQueryFunction {
|
||||
|
||||
public H2JsonQueryFunction(TypeConfiguration typeConfiguration) {
|
||||
super( typeConfiguration, false, true );
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void render(
|
||||
SqlAppender sqlAppender,
|
||||
JsonQueryArguments arguments,
|
||||
ReturnableType<?> returnType,
|
||||
SqlAstTranslator<?> walker) {
|
||||
// Json dereference errors by default if the JSON is invalid
|
||||
if ( arguments.errorBehavior() != null && arguments.errorBehavior() != JsonQueryErrorBehavior.ERROR ) {
|
||||
throw new QueryException( "Can't emulate on error clause on H2" );
|
||||
}
|
||||
if ( arguments.emptyBehavior() == JsonQueryEmptyBehavior.ERROR ) {
|
||||
throw new QueryException( "Can't emulate error on empty clause on H2" );
|
||||
}
|
||||
final String jsonPath;
|
||||
try {
|
||||
jsonPath = walker.getLiteralValue( arguments.jsonPath() );
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new QueryException( "H2 json_query only support literal json paths, but got " + arguments.jsonPath() );
|
||||
}
|
||||
if ( arguments.wrapMode() == JsonQueryWrapMode.WITH_WRAPPER ) {
|
||||
sqlAppender.appendSql( "'['||" );
|
||||
}
|
||||
|
||||
sqlAppender.appendSql( "stringdecode(btrim(nullif(" );
|
||||
sqlAppender.appendSql( "cast(" );
|
||||
H2JsonValueFunction.renderJsonPath(
|
||||
sqlAppender,
|
||||
arguments.jsonDocument(),
|
||||
arguments.isJsonType(),
|
||||
walker,
|
||||
jsonPath,
|
||||
arguments.passingClause()
|
||||
);
|
||||
sqlAppender.appendSql( " as varchar)" );
|
||||
sqlAppender.appendSql( ",'null'),'\"'))");
|
||||
if ( arguments.wrapMode() == JsonQueryWrapMode.WITH_WRAPPER ) {
|
||||
sqlAppender.appendSql( "||']'" );
|
||||
}
|
||||
}
|
||||
}
|
|
@ -51,9 +51,7 @@ public class JsonExistsFunction extends AbstractSqmSelfRenderingFunctionDescript
|
|||
super(
|
||||
"json_exists",
|
||||
FunctionKind.NORMAL,
|
||||
StandardArgumentsValidators.composite(
|
||||
new ArgumentTypesValidator( StandardArgumentsValidators.between( 2, 3 ), IMPLICIT_JSON, STRING, ANY )
|
||||
),
|
||||
new ArgumentTypesValidator( null, IMPLICIT_JSON, STRING ),
|
||||
StandardFunctionReturnTypeResolvers.invariant( typeConfiguration.standardBasicTypeForJavaType( Boolean.class ) ),
|
||||
StandardFunctionArgumentTypeResolvers.invariant( typeConfiguration, JSON, STRING )
|
||||
);
|
||||
|
|
|
@ -0,0 +1,222 @@
|
|||
/*
|
||||
* 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.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
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.produce.function.StandardFunctionReturnTypeResolvers;
|
||||
import org.hibernate.query.sqm.tree.SqmTypedNode;
|
||||
import org.hibernate.query.sqm.tree.expression.SqmJsonQueryExpression;
|
||||
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.JsonPathPassingClause;
|
||||
import org.hibernate.sql.ast.tree.expression.JsonQueryEmptyBehavior;
|
||||
import org.hibernate.sql.ast.tree.expression.JsonQueryErrorBehavior;
|
||||
import org.hibernate.sql.ast.tree.expression.JsonQueryWrapMode;
|
||||
import org.hibernate.type.SqlTypes;
|
||||
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_query function.
|
||||
*/
|
||||
public class JsonQueryFunction extends AbstractSqmSelfRenderingFunctionDescriptor {
|
||||
|
||||
protected final boolean supportsJsonPathExpression;
|
||||
protected final boolean supportsJsonPathPassingClause;
|
||||
|
||||
public JsonQueryFunction(
|
||||
TypeConfiguration typeConfiguration,
|
||||
boolean supportsJsonPathExpression,
|
||||
boolean supportsJsonPathPassingClause) {
|
||||
super(
|
||||
"json_query",
|
||||
FunctionKind.NORMAL,
|
||||
StandardArgumentsValidators.composite(
|
||||
new ArgumentTypesValidator( StandardArgumentsValidators.between( 2, 3 ), IMPLICIT_JSON, STRING, ANY )
|
||||
),
|
||||
StandardFunctionReturnTypeResolvers.invariant(
|
||||
typeConfiguration.getBasicTypeRegistry().resolve( String.class, SqlTypes.JSON )
|
||||
),
|
||||
StandardFunctionArgumentTypeResolvers.invariant( typeConfiguration, JSON, STRING )
|
||||
);
|
||||
this.supportsJsonPathExpression = supportsJsonPathExpression;
|
||||
this.supportsJsonPathPassingClause = supportsJsonPathPassingClause;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected <T> SelfRenderingSqmFunction<T> generateSqmFunctionExpression(
|
||||
List<? extends SqmTypedNode<?>> arguments,
|
||||
ReturnableType<T> impliedResultType,
|
||||
QueryEngine queryEngine) {
|
||||
//noinspection unchecked
|
||||
return (SelfRenderingSqmFunction<T>) new SqmJsonQueryExpression(
|
||||
this,
|
||||
this,
|
||||
arguments,
|
||||
(ReturnableType<String>) impliedResultType,
|
||||
getArgumentsValidator(),
|
||||
getReturnTypeResolver(),
|
||||
queryEngine.getCriteriaBuilder(),
|
||||
getName()
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(
|
||||
SqlAppender sqlAppender,
|
||||
List<? extends SqlAstNode> sqlAstArguments,
|
||||
ReturnableType<?> returnType,
|
||||
SqlAstTranslator<?> walker) {
|
||||
render( sqlAppender, JsonQueryArguments.extract( sqlAstArguments ), returnType, walker );
|
||||
}
|
||||
|
||||
protected void render(
|
||||
SqlAppender sqlAppender,
|
||||
JsonQueryArguments arguments,
|
||||
ReturnableType<?> returnType,
|
||||
SqlAstTranslator<?> walker) {
|
||||
sqlAppender.appendSql( "json_query(" );
|
||||
arguments.jsonDocument().accept( walker );
|
||||
sqlAppender.appendSql( ',' );
|
||||
final JsonPathPassingClause passingClause = arguments.passingClause();
|
||||
if ( supportsJsonPathPassingClause || passingClause == null ) {
|
||||
if ( supportsJsonPathExpression ) {
|
||||
arguments.jsonPath().accept( walker );
|
||||
}
|
||||
else {
|
||||
walker.getSessionFactory().getJdbcServices().getDialect().appendLiteral(
|
||||
sqlAppender,
|
||||
walker.getLiteralValue( arguments.jsonPath() )
|
||||
);
|
||||
}
|
||||
if ( passingClause != null ) {
|
||||
sqlAppender.appendSql( " passing " );
|
||||
final Map<String, Expression> passingExpressions = passingClause.getPassingExpressions();
|
||||
final Iterator<Map.Entry<String, Expression>> iterator = passingExpressions.entrySet().iterator();
|
||||
Map.Entry<String, Expression> entry = iterator.next();
|
||||
entry.getValue().accept( walker );
|
||||
sqlAppender.appendSql( " as " );
|
||||
sqlAppender.appendDoubleQuoteEscapedString( entry.getKey() );
|
||||
while ( iterator.hasNext() ) {
|
||||
entry = iterator.next();
|
||||
sqlAppender.appendSql( ',' );
|
||||
entry.getValue().accept( walker );
|
||||
sqlAppender.appendSql( " as " );
|
||||
sqlAppender.appendDoubleQuoteEscapedString( entry.getKey() );
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
JsonPathHelper.appendInlinedJsonPathIncludingPassingClause(
|
||||
sqlAppender,
|
||||
"",
|
||||
arguments.jsonPath(),
|
||||
passingClause,
|
||||
walker
|
||||
);
|
||||
}
|
||||
if ( arguments.wrapMode() != null ) {
|
||||
switch ( arguments.wrapMode() ) {
|
||||
case WITH_WRAPPER -> sqlAppender.appendSql( " with wrapper" );
|
||||
case WITHOUT_WRAPPER -> sqlAppender.appendSql( " without wrapper" );
|
||||
case WITH_CONDITIONAL_WRAPPER -> sqlAppender.appendSql( " with conditional wrapper" );
|
||||
}
|
||||
}
|
||||
if ( arguments.errorBehavior() != null ) {
|
||||
switch ( arguments.errorBehavior() ) {
|
||||
case ERROR -> sqlAppender.appendSql( " error on error" );
|
||||
case NULL -> sqlAppender.appendSql( " null on error" );
|
||||
case EMPTY_OBJECT -> sqlAppender.appendSql( " empty object on error" );
|
||||
case EMPTY_ARRAY -> sqlAppender.appendSql( " empty array on error" );
|
||||
}
|
||||
}
|
||||
if ( arguments.emptyBehavior() != null ) {
|
||||
switch ( arguments.emptyBehavior() ) {
|
||||
case ERROR -> sqlAppender.appendSql( " error on empty" );
|
||||
case NULL -> sqlAppender.appendSql( " null on empty" );
|
||||
case EMPTY_OBJECT -> sqlAppender.appendSql( " empty object on empty" );
|
||||
case EMPTY_ARRAY -> sqlAppender.appendSql( " empty array on empty" );
|
||||
}
|
||||
}
|
||||
sqlAppender.appendSql( ')' );
|
||||
}
|
||||
|
||||
protected record JsonQueryArguments(
|
||||
Expression jsonDocument,
|
||||
Expression jsonPath,
|
||||
boolean isJsonType,
|
||||
@Nullable JsonPathPassingClause passingClause,
|
||||
@Nullable JsonQueryWrapMode wrapMode,
|
||||
@Nullable JsonQueryErrorBehavior errorBehavior,
|
||||
@Nullable JsonQueryEmptyBehavior emptyBehavior) {
|
||||
public static JsonQueryArguments extract(List<? extends SqlAstNode> sqlAstArguments) {
|
||||
int nextIndex = 2;
|
||||
JsonPathPassingClause passingClause = null;
|
||||
JsonQueryWrapMode wrapMode = null;
|
||||
JsonQueryErrorBehavior errorBehavior = null;
|
||||
JsonQueryEmptyBehavior emptyBehavior = null;
|
||||
if ( nextIndex < sqlAstArguments.size() ) {
|
||||
final SqlAstNode node = sqlAstArguments.get( nextIndex );
|
||||
if ( node instanceof JsonPathPassingClause ) {
|
||||
passingClause = (JsonPathPassingClause) node;
|
||||
nextIndex++;
|
||||
}
|
||||
}
|
||||
if ( nextIndex < sqlAstArguments.size() ) {
|
||||
final SqlAstNode node = sqlAstArguments.get( nextIndex );
|
||||
if ( node instanceof JsonQueryWrapMode ) {
|
||||
wrapMode = (JsonQueryWrapMode) node;
|
||||
nextIndex++;
|
||||
}
|
||||
}
|
||||
if ( nextIndex < sqlAstArguments.size() ) {
|
||||
final SqlAstNode node = sqlAstArguments.get( nextIndex );
|
||||
if ( node instanceof JsonQueryErrorBehavior ) {
|
||||
errorBehavior = (JsonQueryErrorBehavior) node;
|
||||
nextIndex++;
|
||||
}
|
||||
}
|
||||
if ( nextIndex < sqlAstArguments.size() ) {
|
||||
final SqlAstNode node = sqlAstArguments.get( nextIndex );
|
||||
if ( node instanceof JsonQueryEmptyBehavior ) {
|
||||
emptyBehavior = (JsonQueryEmptyBehavior) node;
|
||||
}
|
||||
}
|
||||
final Expression jsonDocument = (Expression) sqlAstArguments.get( 0 );
|
||||
return new JsonQueryArguments(
|
||||
jsonDocument,
|
||||
(Expression) sqlAstArguments.get( 1 ),
|
||||
jsonDocument.getExpressionType() != null
|
||||
&& jsonDocument.getExpressionType().getSingleJdbcMapping().getJdbcType().isJson(),
|
||||
passingClause,
|
||||
wrapMode,
|
||||
errorBehavior,
|
||||
emptyBehavior
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -53,7 +53,7 @@ public class JsonValueFunction extends AbstractSqmSelfRenderingFunctionDescripto
|
|||
"json_value",
|
||||
FunctionKind.NORMAL,
|
||||
StandardArgumentsValidators.composite(
|
||||
new ArgumentTypesValidator( StandardArgumentsValidators.between( 2, 5 ), IMPLICIT_JSON, STRING, ANY, ANY, ANY )
|
||||
new ArgumentTypesValidator( StandardArgumentsValidators.between( 2, 3 ), IMPLICIT_JSON, STRING, ANY )
|
||||
),
|
||||
new CastTargetReturnTypeResolver( typeConfiguration ),
|
||||
StandardFunctionArgumentTypeResolvers.invariant( typeConfiguration, JSON, STRING )
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
* 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.JsonPathPassingClause;
|
||||
import org.hibernate.sql.ast.tree.expression.JsonQueryEmptyBehavior;
|
||||
import org.hibernate.sql.ast.tree.expression.JsonQueryErrorBehavior;
|
||||
import org.hibernate.type.spi.TypeConfiguration;
|
||||
|
||||
/**
|
||||
* MySQL json_query function.
|
||||
*/
|
||||
public class MySQLJsonQueryFunction extends JsonQueryFunction {
|
||||
|
||||
public MySQLJsonQueryFunction(TypeConfiguration typeConfiguration) {
|
||||
super( typeConfiguration, true, false );
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void render(
|
||||
SqlAppender sqlAppender,
|
||||
JsonQueryArguments arguments,
|
||||
ReturnableType<?> returnType,
|
||||
SqlAstTranslator<?> walker) {
|
||||
// json_extract errors by default
|
||||
if ( arguments.errorBehavior() != null && arguments.errorBehavior() != JsonQueryErrorBehavior.ERROR
|
||||
|| arguments.emptyBehavior() == JsonQueryEmptyBehavior.ERROR
|
||||
// Can't emulate DEFAULT ON EMPTY since we can't differentiate between a NULL value and EMPTY
|
||||
|| arguments.emptyBehavior() != null && arguments.emptyBehavior() != JsonQueryEmptyBehavior.NULL ) {
|
||||
super.render( sqlAppender, arguments, returnType, walker );
|
||||
}
|
||||
else {
|
||||
sqlAppender.appendSql( "nullif(json_extract(" );
|
||||
arguments.jsonDocument().accept( walker );
|
||||
sqlAppender.appendSql( "," );
|
||||
final JsonPathPassingClause passingClause = arguments.passingClause();
|
||||
if ( passingClause == null ) {
|
||||
arguments.jsonPath().accept( walker );
|
||||
}
|
||||
else {
|
||||
JsonPathHelper.appendJsonPathConcatPassingClause(
|
||||
sqlAppender,
|
||||
arguments.jsonPath(),
|
||||
passingClause, walker
|
||||
);
|
||||
}
|
||||
sqlAppender.appendSql( "),cast('null' as json))" );
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,81 @@
|
|||
/*
|
||||
* 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.Map;
|
||||
|
||||
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.JdbcParameter;
|
||||
import org.hibernate.sql.ast.tree.expression.JsonPathPassingClause;
|
||||
import org.hibernate.sql.ast.tree.expression.JsonQueryEmptyBehavior;
|
||||
import org.hibernate.sql.ast.tree.expression.JsonQueryErrorBehavior;
|
||||
import org.hibernate.sql.ast.tree.expression.Literal;
|
||||
import org.hibernate.type.spi.TypeConfiguration;
|
||||
|
||||
/**
|
||||
* PostgreSQL json_query function.
|
||||
*/
|
||||
public class PostgreSQLJsonQueryFunction extends JsonQueryFunction {
|
||||
|
||||
public PostgreSQLJsonQueryFunction(TypeConfiguration typeConfiguration) {
|
||||
super( typeConfiguration, true, true );
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void render(
|
||||
SqlAppender sqlAppender,
|
||||
JsonQueryArguments arguments,
|
||||
ReturnableType<?> returnType,
|
||||
SqlAstTranslator<?> walker) {
|
||||
// jsonb_path_query_first errors by default
|
||||
if ( arguments.errorBehavior() != null && arguments.errorBehavior() != JsonQueryErrorBehavior.ERROR ) {
|
||||
throw new QueryException( "Can't emulate on error clause on PostgreSQL" );
|
||||
}
|
||||
if ( arguments.emptyBehavior() != null && arguments.emptyBehavior() != JsonQueryEmptyBehavior.NULL ) {
|
||||
throw new QueryException( "Can't emulate on empty clause on PostgreSQL" );
|
||||
}
|
||||
sqlAppender.appendSql( "jsonb_path_query_array(" );
|
||||
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)" );
|
||||
}
|
||||
final JsonPathPassingClause passingClause = arguments.passingClause();
|
||||
if ( passingClause != null ) {
|
||||
sqlAppender.append( ",jsonb_build_object" );
|
||||
char separator = '(';
|
||||
for ( Map.Entry<String, Expression> entry : passingClause.getPassingExpressions().entrySet() ) {
|
||||
sqlAppender.append( separator );
|
||||
sqlAppender.appendSingleQuoteEscapedString( entry.getKey() );
|
||||
sqlAppender.append( ',' );
|
||||
entry.getValue().accept( walker );
|
||||
separator = ',';
|
||||
}
|
||||
sqlAppender.append( ')' );
|
||||
}
|
||||
// Unquote the value
|
||||
sqlAppender.appendSql( ")#>>'{}'" );
|
||||
}
|
||||
}
|
|
@ -0,0 +1,164 @@
|
|||
/*
|
||||
* 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.expression.JsonPathPassingClause;
|
||||
import org.hibernate.sql.ast.tree.expression.JsonQueryEmptyBehavior;
|
||||
import org.hibernate.sql.ast.tree.expression.JsonQueryErrorBehavior;
|
||||
import org.hibernate.sql.ast.tree.expression.JsonQueryWrapMode;
|
||||
import org.hibernate.type.spi.TypeConfiguration;
|
||||
|
||||
/**
|
||||
* SQL Server json_query function.
|
||||
*/
|
||||
public class SQLServerJsonQueryFunction extends JsonQueryFunction {
|
||||
|
||||
public SQLServerJsonQueryFunction(TypeConfiguration typeConfiguration) {
|
||||
super( typeConfiguration, true, false );
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void render(
|
||||
SqlAppender sqlAppender,
|
||||
JsonQueryArguments arguments,
|
||||
ReturnableType<?> returnType,
|
||||
SqlAstTranslator<?> walker) {
|
||||
// openjson errors by default
|
||||
if ( arguments.errorBehavior() != null && arguments.errorBehavior() != JsonQueryErrorBehavior.ERROR ) {
|
||||
throw new QueryException( "Can't emulate on error clause on SQL server" );
|
||||
}
|
||||
final List<JsonPathHelper.JsonPathElement> jsonPathElements = JsonPathHelper.parseJsonPathElements(
|
||||
walker.getLiteralValue( arguments.jsonPath() )
|
||||
);
|
||||
if ( arguments.emptyBehavior() == JsonQueryEmptyBehavior.EMPTY_ARRAY
|
||||
|| arguments.emptyBehavior() == JsonQueryEmptyBehavior.EMPTY_OBJECT ) {
|
||||
sqlAppender.appendSql( "coalesce(" );
|
||||
}
|
||||
render( sqlAppender, arguments, jsonPathElements, jsonPathElements.size() - 1, walker );
|
||||
if ( arguments.emptyBehavior() == JsonQueryEmptyBehavior.EMPTY_ARRAY ) {
|
||||
sqlAppender.appendSql( ",'[]')" );
|
||||
}
|
||||
else if ( arguments.emptyBehavior() == JsonQueryEmptyBehavior.EMPTY_OBJECT ) {
|
||||
sqlAppender.appendSql( ",'{}')" );
|
||||
}
|
||||
}
|
||||
|
||||
private void render(
|
||||
SqlAppender sqlAppender,
|
||||
JsonQueryArguments arguments,
|
||||
List<JsonPathHelper.JsonPathElement> jsonPathElements,
|
||||
int index,
|
||||
SqlAstTranslator<?> walker) {
|
||||
sqlAppender.appendSql( "(select " );
|
||||
final boolean aggregate = index == jsonPathElements.size() - 1 && (
|
||||
arguments.wrapMode() == JsonQueryWrapMode.WITH_WRAPPER
|
||||
|| arguments.wrapMode() == JsonQueryWrapMode.WITH_CONDITIONAL_WRAPPER
|
||||
);
|
||||
if ( aggregate ) {
|
||||
if ( arguments.wrapMode() == JsonQueryWrapMode.WITH_WRAPPER ) {
|
||||
sqlAppender.appendSql( "'['+" );
|
||||
}
|
||||
else {
|
||||
sqlAppender.appendSql( "case when count(*)>1 then '[' else '' end+" );
|
||||
}
|
||||
sqlAppender.appendSql( "string_agg(t.v,',')" );
|
||||
if ( arguments.wrapMode() == JsonQueryWrapMode.WITH_WRAPPER ) {
|
||||
sqlAppender.appendSql( "+']'" );
|
||||
}
|
||||
else {
|
||||
sqlAppender.appendSql( "+case when count(*)>1 then ']' else '' end" );
|
||||
}
|
||||
|
||||
// openjson unquotes values, so we have to quote them again
|
||||
sqlAppender.appendSql( " from (select " );
|
||||
// type 0 is a null literal
|
||||
sqlAppender.appendSql( "case t.type when 0 then 'null' when 1 then ");
|
||||
// type 1 is a string literal. to quote it, we use for json path and trim the string down to just the value
|
||||
sqlAppender.appendSql(
|
||||
"(select substring(a.v,6,len(a.v)-6) from (select t.value a for json path,without_array_wrapper) a(v))" );
|
||||
sqlAppender.appendSql( " else t.value end v");
|
||||
|
||||
}
|
||||
else {
|
||||
sqlAppender.appendSql( "t.value" );
|
||||
}
|
||||
sqlAppender.appendSql( " from openjson(" );
|
||||
if ( index == 0 ) {
|
||||
arguments.jsonDocument().accept( walker );
|
||||
}
|
||||
else {
|
||||
render( sqlAppender, arguments, jsonPathElements, index - 1, walker );
|
||||
}
|
||||
sqlAppender.appendSql( ')' );
|
||||
if ( arguments.emptyBehavior() == JsonQueryEmptyBehavior.ERROR ) {
|
||||
sqlAppender.appendSql( " with (value nvarchar(max) " );
|
||||
final JsonPathHelper.JsonPathElement jsonPathElement = jsonPathElements.get( index );
|
||||
if ( jsonPathElement instanceof JsonPathHelper.JsonAttribute attribute ) {
|
||||
sqlAppender.appendSql( "'strict $." );
|
||||
final String name = attribute.attribute();
|
||||
for ( int i = 0; i < name.length(); i++ ) {
|
||||
final char c = name.charAt( i );
|
||||
if ( c == '\'' ) {
|
||||
sqlAppender.append( '\'' );
|
||||
}
|
||||
sqlAppender.append( c );
|
||||
}
|
||||
sqlAppender.append( '\'' );
|
||||
}
|
||||
else if ( jsonPathElement instanceof JsonPathHelper.JsonIndexAccess indexAccess ) {
|
||||
sqlAppender.appendSql( "'strict $[" );
|
||||
sqlAppender.appendSql( indexAccess.index() );
|
||||
sqlAppender.appendSql( "]'" );
|
||||
}
|
||||
else if ( jsonPathElement instanceof JsonPathHelper.JsonParameterIndexAccess indexAccess ) {
|
||||
final JsonPathPassingClause passingClause = arguments.passingClause();
|
||||
assert passingClause != null;
|
||||
final Object literalValue = walker.getLiteralValue(
|
||||
passingClause.getPassingExpressions().get( indexAccess.parameterName() )
|
||||
);
|
||||
sqlAppender.appendSql( "'strict $[" );
|
||||
sqlAppender.appendSql( literalValue.toString() );
|
||||
sqlAppender.appendSql( "]'" );
|
||||
}
|
||||
else {
|
||||
throw new UnsupportedOperationException( "Unsupported JSON path expression: " + jsonPathElement );
|
||||
}
|
||||
sqlAppender.appendSql( " as json) t" );
|
||||
}
|
||||
else {
|
||||
sqlAppender.appendSql( " t where " );
|
||||
final JsonPathHelper.JsonPathElement jsonPathElement = jsonPathElements.get( index );
|
||||
if ( jsonPathElement instanceof JsonPathHelper.JsonAttribute attribute ) {
|
||||
sqlAppender.appendSql( "t.[key]=" );
|
||||
sqlAppender.appendSingleQuoteEscapedString( attribute.attribute() );
|
||||
}
|
||||
else if ( jsonPathElement instanceof JsonPathHelper.JsonIndexAccess indexAccess ) {
|
||||
sqlAppender.appendSql( "t.[key]=" );
|
||||
sqlAppender.appendSql( indexAccess.index() );
|
||||
}
|
||||
else if ( jsonPathElement instanceof JsonPathHelper.JsonParameterIndexAccess indexAccess ) {
|
||||
final JsonPathPassingClause passingClause = arguments.passingClause();
|
||||
assert passingClause != null;
|
||||
sqlAppender.appendSql( "t.[key]=" );
|
||||
passingClause.getPassingExpressions().get( indexAccess.parameterName() ).accept( walker );
|
||||
}
|
||||
else {
|
||||
throw new UnsupportedOperationException( "Unsupported JSON path expression: " + jsonPathElement );
|
||||
}
|
||||
}
|
||||
if ( aggregate ) {
|
||||
sqlAppender.appendSql( ") t" );
|
||||
}
|
||||
sqlAppender.appendSql( ")" );
|
||||
}
|
||||
}
|
|
@ -41,7 +41,7 @@ public class SQLServerJsonValueFunction extends JsonValueFunction {
|
|||
arguments.returningType().accept( walker );
|
||||
}
|
||||
else {
|
||||
sqlAppender.appendSql( "varchar(max)" );
|
||||
sqlAppender.appendSql( "nvarchar(max)" );
|
||||
}
|
||||
sqlAppender.appendSql( ' ' );
|
||||
final JsonPathPassingClause passingClause = arguments.passingClause();
|
||||
|
|
|
@ -3713,6 +3713,20 @@ public interface HibernateCriteriaBuilder extends CriteriaBuilder {
|
|||
@Incubating
|
||||
<T> JpaJsonValueExpression<T> jsonValue(Expression<?> jsonDocument, Expression<String> jsonPath, Class<T> returningType);
|
||||
|
||||
/**
|
||||
* @see #jsonQuery(Expression, Expression)
|
||||
* @since 7.0
|
||||
*/
|
||||
@Incubating
|
||||
JpaJsonQueryExpression jsonQuery(Expression<?> jsonDocument, String jsonPath);
|
||||
|
||||
/**
|
||||
* Queries values by JSON path from a JSON document.
|
||||
* @since 7.0
|
||||
*/
|
||||
@Incubating
|
||||
JpaJsonQueryExpression jsonQuery(Expression<?> jsonDocument, Expression<String> jsonPath);
|
||||
|
||||
/**
|
||||
* Checks if a JSON document contains a node for the given JSON path.
|
||||
*
|
||||
|
|
|
@ -56,7 +56,7 @@ public interface JpaJsonExistsExpression extends JpaExpression<Boolean> {
|
|||
JpaJsonExistsExpression passing(String parameterName, Expression<?> expression);
|
||||
|
||||
/**
|
||||
* The behavior of the json value expression when a JSON processing error occurs.
|
||||
* The behavior of the json exists expression when a JSON processing error occurs.
|
||||
*/
|
||||
enum ErrorBehavior {
|
||||
/**
|
||||
|
|
|
@ -0,0 +1,204 @@
|
|||
/*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* A special expression for the {@code json_query} function.
|
||||
* @since 7.0
|
||||
*/
|
||||
@Incubating
|
||||
public interface JpaJsonQueryExpression extends JpaExpression<String> {
|
||||
/**
|
||||
* Get the {@link WrapMode} of this json query expression.
|
||||
*
|
||||
* @return the wrap mode
|
||||
*/
|
||||
WrapMode getWrapMode();
|
||||
/**
|
||||
* Get the {@link ErrorBehavior} of this json query expression.
|
||||
*
|
||||
* @return the error behavior
|
||||
*/
|
||||
ErrorBehavior getErrorBehavior();
|
||||
|
||||
/**
|
||||
* Get the {@link EmptyBehavior} of this json query expression.
|
||||
*
|
||||
* @return the empty behavior
|
||||
*/
|
||||
EmptyBehavior getEmptyBehavior();
|
||||
|
||||
/**
|
||||
* Sets the {@link WrapMode#WITHOUT_WRAPPER} for this json query expression.
|
||||
*
|
||||
* @return {@code this} for method chaining
|
||||
*/
|
||||
JpaJsonQueryExpression withoutWrapper();
|
||||
/**
|
||||
* Sets the {@link WrapMode#WITH_WRAPPER} for this json query expression.
|
||||
*
|
||||
* @return {@code this} for method chaining
|
||||
*/
|
||||
JpaJsonQueryExpression withWrapper();
|
||||
/**
|
||||
* Sets the {@link WrapMode#WITH_CONDITIONAL_WRAPPER} for this json query expression.
|
||||
*
|
||||
* @return {@code this} for method chaining
|
||||
*/
|
||||
JpaJsonQueryExpression withConditionalWrapper();
|
||||
/**
|
||||
* Sets the {@link WrapMode#UNSPECIFIED} for this json query expression.
|
||||
*
|
||||
* @return {@code this} for method chaining
|
||||
*/
|
||||
JpaJsonQueryExpression unspecifiedWrapper();
|
||||
|
||||
/**
|
||||
* Sets the {@link ErrorBehavior#UNSPECIFIED} for this json query expression.
|
||||
*
|
||||
* @return {@code this} for method chaining
|
||||
*/
|
||||
JpaJsonQueryExpression unspecifiedOnError();
|
||||
/**
|
||||
* Sets the {@link ErrorBehavior#ERROR} for this json query expression.
|
||||
*
|
||||
* @return {@code this} for method chaining
|
||||
*/
|
||||
JpaJsonQueryExpression errorOnError();
|
||||
/**
|
||||
* Sets the {@link ErrorBehavior#NULL} for this json query expression.
|
||||
*
|
||||
* @return {@code this} for method chaining
|
||||
*/
|
||||
JpaJsonQueryExpression nullOnError();
|
||||
/**
|
||||
* Sets the {@link ErrorBehavior#EMPTY_ARRAY} for this json query expression.
|
||||
*
|
||||
* @return {@code this} for method chaining
|
||||
*/
|
||||
JpaJsonQueryExpression emptyArrayOnError();
|
||||
/**
|
||||
* Sets the {@link ErrorBehavior#EMPTY_OBJECT} for this json query expression.
|
||||
*
|
||||
* @return {@code this} for method chaining
|
||||
*/
|
||||
JpaJsonQueryExpression emptyObjectOnError();
|
||||
|
||||
/**
|
||||
* Sets the {@link EmptyBehavior#UNSPECIFIED} for this json query expression.
|
||||
*
|
||||
* @return {@code this} for method chaining
|
||||
*/
|
||||
JpaJsonQueryExpression unspecifiedOnEmpty();
|
||||
/**
|
||||
* Sets the {@link EmptyBehavior#ERROR} for this json query expression.
|
||||
*
|
||||
* @return {@code this} for method chaining
|
||||
*/
|
||||
JpaJsonQueryExpression errorOnEmpty();
|
||||
/**
|
||||
* Sets the {@link EmptyBehavior#NULL} for this json query expression.
|
||||
*
|
||||
* @return {@code this} for method chaining
|
||||
*/
|
||||
JpaJsonQueryExpression nullOnEmpty();
|
||||
/**
|
||||
* Sets the {@link EmptyBehavior#EMPTY_ARRAY} for this json query expression.
|
||||
*
|
||||
* @return {@code this} for method chaining
|
||||
*/
|
||||
JpaJsonQueryExpression emptyArrayOnEmpty();
|
||||
/**
|
||||
* Sets the {@link EmptyBehavior#EMPTY_OBJECT} for this json query expression.
|
||||
*
|
||||
* @return {@code this} for method chaining
|
||||
*/
|
||||
JpaJsonQueryExpression emptyObjectOnEmpty();
|
||||
|
||||
/**
|
||||
* Passes the given {@link Expression} as value for the parameter with the given name in the JSON path.
|
||||
*
|
||||
* @return {@code this} for method chaining
|
||||
*/
|
||||
JpaJsonQueryExpression passing(String parameterName, Expression<?> expression);
|
||||
|
||||
/**
|
||||
* The kind of wrapping to apply to the results of the query.
|
||||
*/
|
||||
enum WrapMode {
|
||||
/**
|
||||
* Omit the array wrapper in the result.
|
||||
*/
|
||||
WITHOUT_WRAPPER,
|
||||
/**
|
||||
* Force the array wrapper in the result.
|
||||
*/
|
||||
WITH_WRAPPER,
|
||||
/**
|
||||
* Only use an array wrapper in the result if there is more than one result.
|
||||
*/
|
||||
WITH_CONDITIONAL_WRAPPER,
|
||||
/**
|
||||
* Unspecified behavior i.e. the default database behavior.
|
||||
*/
|
||||
UNSPECIFIED
|
||||
}
|
||||
/**
|
||||
* The behavior of the json query expression when a JSON processing error occurs.
|
||||
*/
|
||||
enum ErrorBehavior {
|
||||
/**
|
||||
* SQL/JDBC error should be raised.
|
||||
*/
|
||||
ERROR,
|
||||
/**
|
||||
* {@code null} should be returned.
|
||||
*/
|
||||
NULL,
|
||||
/**
|
||||
* An empty array should be returned.
|
||||
*/
|
||||
EMPTY_ARRAY,
|
||||
/**
|
||||
* An empty object should be returned.
|
||||
*/
|
||||
EMPTY_OBJECT,
|
||||
/**
|
||||
* Unspecified behavior i.e. the default database behavior.
|
||||
*/
|
||||
UNSPECIFIED
|
||||
}
|
||||
/**
|
||||
* The behavior of the json query 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,
|
||||
/**
|
||||
* An empty array should be returned.
|
||||
*/
|
||||
EMPTY_ARRAY,
|
||||
/**
|
||||
* An empty object should be returned.
|
||||
*/
|
||||
EMPTY_OBJECT,
|
||||
/**
|
||||
* Unspecified behavior i.e. the default database behavior.
|
||||
*/
|
||||
UNSPECIFIED
|
||||
}
|
||||
}
|
|
@ -39,6 +39,7 @@ import org.hibernate.query.criteria.JpaFunction;
|
|||
import org.hibernate.query.criteria.JpaInPredicate;
|
||||
import org.hibernate.query.criteria.JpaJoin;
|
||||
import org.hibernate.query.criteria.JpaJsonExistsExpression;
|
||||
import org.hibernate.query.criteria.JpaJsonQueryExpression;
|
||||
import org.hibernate.query.criteria.JpaJsonValueExpression;
|
||||
import org.hibernate.query.criteria.JpaListJoin;
|
||||
import org.hibernate.query.criteria.JpaMapJoin;
|
||||
|
@ -3376,6 +3377,18 @@ public class HibernateCriteriaBuilderDelegate implements HibernateCriteriaBuilde
|
|||
return criteriaBuilder.jsonValue( jsonDocument, jsonPath, returningType );
|
||||
}
|
||||
|
||||
@Override
|
||||
@Incubating
|
||||
public JpaJsonQueryExpression jsonQuery(Expression<?> jsonDocument, String jsonPath) {
|
||||
return criteriaBuilder.jsonQuery( jsonDocument, jsonPath );
|
||||
}
|
||||
|
||||
@Override
|
||||
@Incubating
|
||||
public JpaJsonQueryExpression jsonQuery(Expression<?> jsonDocument, Expression<String> jsonPath) {
|
||||
return criteriaBuilder.jsonQuery( jsonDocument, jsonPath );
|
||||
}
|
||||
|
||||
@Override
|
||||
@Incubating
|
||||
public JpaJsonExistsExpression jsonExists(Expression<?> jsonDocument, String jsonPath) {
|
||||
|
|
|
@ -146,6 +146,7 @@ import org.hibernate.query.sqm.tree.expression.SqmFunction;
|
|||
import org.hibernate.query.sqm.tree.expression.SqmHqlNumericLiteral;
|
||||
import org.hibernate.query.sqm.tree.expression.SqmJsonExistsExpression;
|
||||
import org.hibernate.query.sqm.tree.expression.SqmJsonNullBehavior;
|
||||
import org.hibernate.query.sqm.tree.expression.SqmJsonQueryExpression;
|
||||
import org.hibernate.query.sqm.tree.expression.SqmJsonValueExpression;
|
||||
import org.hibernate.query.sqm.tree.expression.SqmLiteral;
|
||||
import org.hibernate.query.sqm.tree.expression.SqmLiteralEntityType;
|
||||
|
@ -2745,6 +2746,79 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
|
|||
return jsonValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SqmExpression<?> visitJsonQueryFunction(HqlParser.JsonQueryFunctionContext ctx) {
|
||||
final SqmExpression<?> jsonDocument = (SqmExpression<?>) ctx.expression( 0 ).accept( this );
|
||||
final SqmExpression<?> jsonPath = (SqmExpression<?>) ctx.expression( 1 ).accept( this );
|
||||
final SqmJsonQueryExpression jsonQuery = (SqmJsonQueryExpression) getFunctionDescriptor( "json_query" ).<String>generateSqmExpression(
|
||||
asList( jsonDocument, jsonPath ),
|
||||
null,
|
||||
creationContext.getQueryEngine()
|
||||
);
|
||||
final HqlParser.JsonQueryWrapperClauseContext wrapperClause = ctx.jsonQueryWrapperClause();
|
||||
if ( wrapperClause != null ) {
|
||||
final TerminalNode firstToken = (TerminalNode) wrapperClause.getChild( 0 );
|
||||
if ( firstToken.getSymbol().getType() == HqlParser.WITH ) {
|
||||
final TerminalNode secondToken = (TerminalNode) wrapperClause.getChild( 1 );
|
||||
if ( wrapperClause.getChildCount() > 2 && secondToken.getSymbol().getType() == HqlParser.CONDITIONAL ) {
|
||||
jsonQuery.withConditionalWrapper();
|
||||
}
|
||||
else {
|
||||
jsonQuery.withWrapper();
|
||||
}
|
||||
}
|
||||
else {
|
||||
jsonQuery.withoutWrapper();
|
||||
}
|
||||
}
|
||||
for ( HqlParser.JsonQueryOnErrorOrEmptyClauseContext subCtx : ctx.jsonQueryOnErrorOrEmptyClause() ) {
|
||||
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 -> jsonQuery.nullOnError();
|
||||
case HqlParser.ERROR -> jsonQuery.errorOnError();
|
||||
case HqlParser.EMPTY -> {
|
||||
final TerminalNode secondToken = (TerminalNode) subCtx.getChild( 1 );
|
||||
if ( secondToken.getSymbol().getType() == HqlParser.OBJECT ) {
|
||||
jsonQuery.emptyObjectOnError();
|
||||
}
|
||||
else {
|
||||
jsonQuery.emptyArrayOnError();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
switch ( firstToken.getSymbol().getType() ) {
|
||||
case HqlParser.NULL -> jsonQuery.nullOnEmpty();
|
||||
case HqlParser.ERROR -> jsonQuery.errorOnEmpty();
|
||||
case HqlParser.EMPTY -> {
|
||||
final TerminalNode secondToken = (TerminalNode) subCtx.getChild( 1 );
|
||||
if ( secondToken.getSymbol().getType() == HqlParser.OBJECT ) {
|
||||
jsonQuery.emptyObjectOnEmpty();
|
||||
}
|
||||
else {
|
||||
jsonQuery.emptyArrayOnEmpty();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
final HqlParser.JsonPassingClauseContext passingClause = ctx.jsonPassingClause();
|
||||
if ( passingClause != null ) {
|
||||
final List<HqlParser.ExpressionOrPredicateContext> expressionContexts = passingClause.expressionOrPredicate();
|
||||
final List<HqlParser.IdentifierContext> identifierContexts = passingClause.identifier();
|
||||
for ( int i = 0; i < expressionContexts.size(); i++ ) {
|
||||
jsonQuery.passing(
|
||||
visitIdentifier( identifierContexts.get( i ) ),
|
||||
(SqmExpression<?>) expressionContexts.get( i ).accept( this )
|
||||
);
|
||||
}
|
||||
}
|
||||
return jsonQuery;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SqmExpression<?> visitJsonExistsFunction(HqlParser.JsonExistsFunctionContext ctx) {
|
||||
final SqmExpression<?> jsonDocument = (SqmExpression<?>) ctx.expression( 0 ).accept( this );
|
||||
|
|
|
@ -26,7 +26,6 @@ import org.hibernate.query.criteria.HibernateCriteriaBuilder;
|
|||
import org.hibernate.query.criteria.JpaCoalesce;
|
||||
import org.hibernate.query.criteria.JpaCompoundSelection;
|
||||
import org.hibernate.query.criteria.JpaExpression;
|
||||
import org.hibernate.query.criteria.JpaJsonExistsExpression;
|
||||
import org.hibernate.query.criteria.JpaOrder;
|
||||
import org.hibernate.query.criteria.JpaParameterExpression;
|
||||
import org.hibernate.query.criteria.JpaPredicate;
|
||||
|
@ -46,6 +45,7 @@ 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.SqmJsonExistsExpression;
|
||||
import org.hibernate.query.sqm.tree.expression.SqmJsonQueryExpression;
|
||||
import org.hibernate.query.sqm.tree.expression.SqmJsonValueExpression;
|
||||
import org.hibernate.query.sqm.tree.expression.SqmModifiedSubQueryExpression;
|
||||
import org.hibernate.query.sqm.tree.expression.SqmTuple;
|
||||
|
@ -631,6 +631,12 @@ public interface NodeBuilder extends HibernateCriteriaBuilder, BindingContext {
|
|||
@Override
|
||||
SqmJsonValueExpression<String> jsonValue(Expression<?> jsonDocument, String jsonPath);
|
||||
|
||||
@Override
|
||||
SqmJsonQueryExpression jsonQuery(Expression<?> jsonDocument, Expression<String> jsonPath);
|
||||
|
||||
@Override
|
||||
SqmJsonQueryExpression jsonQuery(Expression<?> jsonDocument, String jsonPath);
|
||||
|
||||
@Override
|
||||
SqmJsonExistsExpression jsonExists(Expression<?> jsonDocument, Expression<String> jsonPath);
|
||||
|
||||
|
|
|
@ -122,6 +122,7 @@ import org.hibernate.query.sqm.tree.expression.SqmFormat;
|
|||
import org.hibernate.query.sqm.tree.expression.SqmFunction;
|
||||
import org.hibernate.query.sqm.tree.expression.SqmJsonExistsExpression;
|
||||
import org.hibernate.query.sqm.tree.expression.SqmJsonNullBehavior;
|
||||
import org.hibernate.query.sqm.tree.expression.SqmJsonQueryExpression;
|
||||
import org.hibernate.query.sqm.tree.expression.SqmJsonValueExpression;
|
||||
import org.hibernate.query.sqm.tree.expression.SqmLiteral;
|
||||
import org.hibernate.query.sqm.tree.expression.SqmLiteralNull;
|
||||
|
@ -5334,6 +5335,20 @@ public class SqmCriteriaNodeBuilder implements NodeBuilder, Serializable {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public SqmJsonQueryExpression jsonQuery(Expression<?> jsonDocument, String jsonPath) {
|
||||
return jsonQuery( jsonDocument, value( jsonPath ) );
|
||||
}
|
||||
|
||||
@Override
|
||||
public SqmJsonQueryExpression jsonQuery(Expression<?> jsonDocument, Expression<String> jsonPath) {
|
||||
return (SqmJsonQueryExpression) getFunctionDescriptor( "json_query" ).<String>generateSqmExpression(
|
||||
asList( (SqmTypedNode<?>) jsonDocument, (SqmTypedNode<?>) jsonPath ),
|
||||
null,
|
||||
queryEngine
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SqmJsonExistsExpression jsonExists(Expression<?> jsonDocument, String jsonPath) {
|
||||
return jsonExists( jsonDocument, value( jsonPath ) );
|
||||
|
|
|
@ -36,7 +36,7 @@ import org.checkerframework.checker.nullness.qual.Nullable;
|
|||
*/
|
||||
@Incubating
|
||||
public class SqmJsonExistsExpression extends AbstractSqmJsonPathExpression<Boolean> implements JpaJsonExistsExpression {
|
||||
private @Nullable ErrorBehavior errorBehavior;
|
||||
private ErrorBehavior errorBehavior = ErrorBehavior.UNSPECIFIED;
|
||||
|
||||
public SqmJsonExistsExpression(
|
||||
SqmFunctionDescriptor descriptor,
|
||||
|
@ -69,7 +69,7 @@ public class SqmJsonExistsExpression extends AbstractSqmJsonPathExpression<Boole
|
|||
NodeBuilder nodeBuilder,
|
||||
String name,
|
||||
@Nullable Map<String, SqmExpression<?>> passingExpressions,
|
||||
@Nullable ErrorBehavior errorBehavior) {
|
||||
ErrorBehavior errorBehavior) {
|
||||
super(
|
||||
descriptor,
|
||||
renderer,
|
||||
|
@ -159,18 +159,10 @@ public class SqmJsonExistsExpression extends AbstractSqmJsonPathExpression<Boole
|
|||
if ( jsonPathPassingClause != null ) {
|
||||
arguments.add( jsonPathPassingClause );
|
||||
}
|
||||
if ( errorBehavior != null ) {
|
||||
switch ( errorBehavior ) {
|
||||
case ERROR:
|
||||
arguments.add( JsonExistsErrorBehavior.ERROR );
|
||||
break;
|
||||
case TRUE:
|
||||
arguments.add( JsonExistsErrorBehavior.TRUE );
|
||||
break;
|
||||
case FALSE:
|
||||
arguments.add( JsonExistsErrorBehavior.FALSE );
|
||||
break;
|
||||
}
|
||||
switch ( errorBehavior ) {
|
||||
case ERROR -> arguments.add( JsonExistsErrorBehavior.ERROR );
|
||||
case TRUE -> arguments.add( JsonExistsErrorBehavior.TRUE );
|
||||
case FALSE -> arguments.add( JsonExistsErrorBehavior.FALSE );
|
||||
}
|
||||
return new SelfRenderingFunctionSqlAstExpression(
|
||||
getFunctionName(),
|
||||
|
@ -189,18 +181,10 @@ public class SqmJsonExistsExpression extends AbstractSqmJsonPathExpression<Boole
|
|||
getArguments().get( 1 ).appendHqlString( sb );
|
||||
|
||||
appendPassingExpressionHqlString( sb );
|
||||
if ( errorBehavior != null ) {
|
||||
switch ( errorBehavior ) {
|
||||
case ERROR:
|
||||
sb.append( " error on error" );
|
||||
break;
|
||||
case TRUE:
|
||||
sb.append( " true on error" );
|
||||
break;
|
||||
case FALSE:
|
||||
sb.append( " false on error" );
|
||||
break;
|
||||
}
|
||||
switch ( errorBehavior ) {
|
||||
case ERROR -> sb.append( " error on error" );
|
||||
case TRUE -> sb.append( " true on error" );
|
||||
case FALSE -> sb.append( " false on error" );
|
||||
}
|
||||
sb.append( ')' );
|
||||
}
|
||||
|
|
|
@ -0,0 +1,295 @@
|
|||
/*
|
||||
* 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 java.util.Map;
|
||||
|
||||
import org.hibernate.Incubating;
|
||||
import org.hibernate.query.ReturnableType;
|
||||
import org.hibernate.query.criteria.JpaJsonQueryExpression;
|
||||
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.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.JsonPathPassingClause;
|
||||
import org.hibernate.sql.ast.tree.expression.JsonQueryEmptyBehavior;
|
||||
import org.hibernate.sql.ast.tree.expression.JsonQueryErrorBehavior;
|
||||
import org.hibernate.sql.ast.tree.expression.JsonQueryWrapMode;
|
||||
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
/**
|
||||
* Special expression for the json_query function that also captures special syntax elements like error and empty behavior.
|
||||
*
|
||||
* @since 7.0
|
||||
*/
|
||||
@Incubating
|
||||
public class SqmJsonQueryExpression extends AbstractSqmJsonPathExpression<String> implements JpaJsonQueryExpression {
|
||||
private WrapMode wrapMode = WrapMode.UNSPECIFIED;
|
||||
private ErrorBehavior errorBehavior = ErrorBehavior.UNSPECIFIED;
|
||||
private EmptyBehavior emptyBehavior = EmptyBehavior.UNSPECIFIED;
|
||||
|
||||
public SqmJsonQueryExpression(
|
||||
SqmFunctionDescriptor descriptor,
|
||||
FunctionRenderer renderer,
|
||||
List<? extends SqmTypedNode<?>> arguments,
|
||||
@Nullable ReturnableType<String> impliedResultType,
|
||||
@Nullable ArgumentsValidator argumentsValidator,
|
||||
FunctionReturnTypeResolver returnTypeResolver,
|
||||
NodeBuilder nodeBuilder,
|
||||
String name) {
|
||||
super(
|
||||
descriptor,
|
||||
renderer,
|
||||
arguments,
|
||||
impliedResultType,
|
||||
argumentsValidator,
|
||||
returnTypeResolver,
|
||||
nodeBuilder,
|
||||
name
|
||||
);
|
||||
}
|
||||
|
||||
private SqmJsonQueryExpression(
|
||||
SqmFunctionDescriptor descriptor,
|
||||
FunctionRenderer renderer,
|
||||
List<? extends SqmTypedNode<?>> arguments,
|
||||
@Nullable ReturnableType<String> impliedResultType,
|
||||
@Nullable ArgumentsValidator argumentsValidator,
|
||||
FunctionReturnTypeResolver returnTypeResolver,
|
||||
NodeBuilder nodeBuilder,
|
||||
String name,
|
||||
@Nullable Map<String, SqmExpression<?>> passingExpressions,
|
||||
WrapMode wrapMode,
|
||||
ErrorBehavior errorBehavior,
|
||||
EmptyBehavior emptyBehavior) {
|
||||
super(
|
||||
descriptor,
|
||||
renderer,
|
||||
arguments,
|
||||
impliedResultType,
|
||||
argumentsValidator,
|
||||
returnTypeResolver,
|
||||
nodeBuilder,
|
||||
name,
|
||||
passingExpressions
|
||||
);
|
||||
this.wrapMode = wrapMode;
|
||||
this.errorBehavior = errorBehavior;
|
||||
this.emptyBehavior = emptyBehavior;
|
||||
}
|
||||
|
||||
public SqmJsonQueryExpression copy(SqmCopyContext context) {
|
||||
final SqmJsonQueryExpression 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 SqmJsonQueryExpression(
|
||||
getFunctionDescriptor(),
|
||||
getFunctionRenderer(),
|
||||
arguments,
|
||||
getImpliedResultType(),
|
||||
getArgumentsValidator(),
|
||||
getReturnTypeResolver(),
|
||||
nodeBuilder(),
|
||||
getFunctionName(),
|
||||
copyPassingExpressions( context ),
|
||||
wrapMode,
|
||||
errorBehavior,
|
||||
emptyBehavior
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public WrapMode getWrapMode() {
|
||||
return wrapMode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ErrorBehavior getErrorBehavior() {
|
||||
return errorBehavior;
|
||||
}
|
||||
|
||||
@Override
|
||||
public EmptyBehavior getEmptyBehavior() {
|
||||
return emptyBehavior;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SqmJsonQueryExpression withoutWrapper() {
|
||||
this.wrapMode = WrapMode.WITHOUT_WRAPPER;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SqmJsonQueryExpression withWrapper() {
|
||||
this.wrapMode = WrapMode.WITH_WRAPPER;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SqmJsonQueryExpression withConditionalWrapper() {
|
||||
this.wrapMode = WrapMode.WITH_CONDITIONAL_WRAPPER;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SqmJsonQueryExpression unspecifiedWrapper() {
|
||||
this.wrapMode = WrapMode.UNSPECIFIED;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SqmJsonQueryExpression unspecifiedOnError() {
|
||||
this.errorBehavior = ErrorBehavior.UNSPECIFIED;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SqmJsonQueryExpression errorOnError() {
|
||||
this.errorBehavior = ErrorBehavior.ERROR;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SqmJsonQueryExpression nullOnError() {
|
||||
this.errorBehavior = ErrorBehavior.NULL;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SqmJsonQueryExpression emptyArrayOnError() {
|
||||
this.errorBehavior = ErrorBehavior.EMPTY_ARRAY;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SqmJsonQueryExpression emptyObjectOnError() {
|
||||
this.errorBehavior = ErrorBehavior.EMPTY_OBJECT;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SqmJsonQueryExpression unspecifiedOnEmpty() {
|
||||
this.errorBehavior = ErrorBehavior.UNSPECIFIED;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SqmJsonQueryExpression errorOnEmpty() {
|
||||
this.emptyBehavior = EmptyBehavior.ERROR;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SqmJsonQueryExpression nullOnEmpty() {
|
||||
this.emptyBehavior = EmptyBehavior.NULL;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SqmJsonQueryExpression emptyArrayOnEmpty() {
|
||||
this.emptyBehavior = EmptyBehavior.EMPTY_ARRAY;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SqmJsonQueryExpression emptyObjectOnEmpty() {
|
||||
this.emptyBehavior = EmptyBehavior.EMPTY_OBJECT;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SqmJsonQueryExpression passing(
|
||||
String parameterName,
|
||||
jakarta.persistence.criteria.Expression<?> expression) {
|
||||
addPassingExpression( parameterName, (SqmExpression<?>) 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() );
|
||||
}
|
||||
final JsonPathPassingClause jsonPathPassingClause = createJsonPathPassingClause( walker );
|
||||
if ( jsonPathPassingClause != null ) {
|
||||
arguments.add( jsonPathPassingClause );
|
||||
}
|
||||
switch ( wrapMode ) {
|
||||
case WITH_WRAPPER -> arguments.add( JsonQueryWrapMode.WITH_WRAPPER );
|
||||
case WITHOUT_WRAPPER -> arguments.add( JsonQueryWrapMode.WITHOUT_WRAPPER );
|
||||
case WITH_CONDITIONAL_WRAPPER -> arguments.add( JsonQueryWrapMode.WITH_CONDITIONAL_WRAPPER );
|
||||
}
|
||||
switch ( errorBehavior ) {
|
||||
case NULL -> arguments.add( JsonQueryErrorBehavior.NULL );
|
||||
case ERROR -> arguments.add( JsonQueryErrorBehavior.ERROR );
|
||||
case EMPTY_OBJECT -> arguments.add( JsonQueryErrorBehavior.EMPTY_OBJECT );
|
||||
case EMPTY_ARRAY -> arguments.add( JsonQueryErrorBehavior.EMPTY_ARRAY );
|
||||
}
|
||||
switch ( emptyBehavior ) {
|
||||
case NULL -> arguments.add( JsonQueryEmptyBehavior.NULL );
|
||||
case ERROR -> arguments.add( JsonQueryEmptyBehavior.ERROR );
|
||||
case EMPTY_OBJECT -> arguments.add( JsonQueryEmptyBehavior.EMPTY_OBJECT );
|
||||
case EMPTY_ARRAY -> arguments.add( JsonQueryEmptyBehavior.EMPTY_ARRAY );
|
||||
}
|
||||
return new SelfRenderingFunctionSqlAstExpression(
|
||||
getFunctionName(),
|
||||
getFunctionRenderer(),
|
||||
arguments,
|
||||
resultType,
|
||||
resultType == null ? null : getMappingModelExpressible( walker, resultType, arguments )
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void appendHqlString(StringBuilder sb) {
|
||||
sb.append( "json_query(" );
|
||||
getArguments().get( 0 ).appendHqlString( sb );
|
||||
sb.append( ',' );
|
||||
getArguments().get( 1 ).appendHqlString( sb );
|
||||
|
||||
appendPassingExpressionHqlString( sb );
|
||||
switch ( wrapMode ) {
|
||||
case WITH_WRAPPER -> sb.append( " with wrapper" );
|
||||
case WITHOUT_WRAPPER -> sb.append( " without wrapper" );
|
||||
case WITH_CONDITIONAL_WRAPPER -> sb.append( " with conditional wrapper" );
|
||||
}
|
||||
switch ( errorBehavior ) {
|
||||
case NULL -> sb.append( " null on error" );
|
||||
case ERROR -> sb.append( " error on error" );
|
||||
case EMPTY_ARRAY -> sb.append( " empty array on error" );
|
||||
case EMPTY_OBJECT -> sb.append( " empty object on error" );
|
||||
}
|
||||
switch ( emptyBehavior ) {
|
||||
case NULL -> sb.append( " null on empty" );
|
||||
case ERROR -> sb.append( " error on empty" );
|
||||
case EMPTY_ARRAY -> sb.append( " empty array on empty" );
|
||||
case EMPTY_OBJECT -> sb.append( " empty object on empty" );
|
||||
}
|
||||
sb.append( ')' );
|
||||
}
|
||||
}
|
|
@ -38,9 +38,9 @@ import org.checkerframework.checker.nullness.qual.Nullable;
|
|||
*/
|
||||
@Incubating
|
||||
public class SqmJsonValueExpression<T> extends AbstractSqmJsonPathExpression<T> implements JpaJsonValueExpression<T> {
|
||||
private @Nullable ErrorBehavior errorBehavior;
|
||||
private ErrorBehavior errorBehavior = ErrorBehavior.UNSPECIFIED;
|
||||
private SqmExpression<T> errorDefaultExpression;
|
||||
private @Nullable EmptyBehavior emptyBehavior;
|
||||
private EmptyBehavior emptyBehavior = EmptyBehavior.UNSPECIFIED;
|
||||
private SqmExpression<T> emptyDefaultExpression;
|
||||
|
||||
public SqmJsonValueExpression(
|
||||
|
@ -74,9 +74,9 @@ public class SqmJsonValueExpression<T> extends AbstractSqmJsonPathExpression<T>
|
|||
NodeBuilder nodeBuilder,
|
||||
String name,
|
||||
@Nullable Map<String, SqmExpression<?>> passingExpressions,
|
||||
@Nullable ErrorBehavior errorBehavior,
|
||||
ErrorBehavior errorBehavior,
|
||||
SqmExpression<T> errorDefaultExpression,
|
||||
@Nullable EmptyBehavior emptyBehavior,
|
||||
EmptyBehavior emptyBehavior,
|
||||
SqmExpression<T> emptyDefaultExpression) {
|
||||
super(
|
||||
descriptor,
|
||||
|
@ -222,35 +222,19 @@ public class SqmJsonValueExpression<T> extends AbstractSqmJsonPathExpression<T>
|
|||
if ( jsonPathPassingClause != null ) {
|
||||
arguments.add( jsonPathPassingClause );
|
||||
}
|
||||
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;
|
||||
}
|
||||
switch ( errorBehavior ) {
|
||||
case NULL -> arguments.add( JsonValueErrorBehavior.NULL );
|
||||
case ERROR -> arguments.add( JsonValueErrorBehavior.ERROR );
|
||||
case DEFAULT -> arguments.add( JsonValueErrorBehavior.defaultOnError(
|
||||
(Expression) errorDefaultExpression.accept( walker )
|
||||
) );
|
||||
}
|
||||
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;
|
||||
}
|
||||
switch ( emptyBehavior ) {
|
||||
case NULL -> arguments.add( JsonValueEmptyBehavior.NULL );
|
||||
case ERROR -> arguments.add( JsonValueEmptyBehavior.ERROR );
|
||||
case DEFAULT -> arguments.add( JsonValueEmptyBehavior.defaultOnEmpty(
|
||||
(Expression) emptyDefaultExpression.accept( walker )
|
||||
) );
|
||||
}
|
||||
return new SelfRenderingFunctionSqlAstExpression(
|
||||
getFunctionName(),
|
||||
|
@ -273,34 +257,22 @@ public class SqmJsonValueExpression<T> extends AbstractSqmJsonPathExpression<T>
|
|||
sb.append( " returning " );
|
||||
getArguments().get( 2 ).appendHqlString( sb );
|
||||
}
|
||||
if ( errorBehavior != null ) {
|
||||
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 ( errorBehavior ) {
|
||||
case NULL -> sb.append( " null on error" );
|
||||
case ERROR -> sb.append( " error on error" );
|
||||
case DEFAULT -> {
|
||||
sb.append( " default " );
|
||||
errorDefaultExpression.appendHqlString( sb );
|
||||
sb.append( " on error" );
|
||||
}
|
||||
}
|
||||
if ( emptyBehavior != null ) {
|
||||
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;
|
||||
switch ( emptyBehavior ) {
|
||||
case NULL -> sb.append( " null on empty" );
|
||||
case ERROR -> sb.append( " error on empty" );
|
||||
case DEFAULT -> {
|
||||
sb.append( " default " );
|
||||
emptyDefaultExpression.appendHqlString( sb );
|
||||
sb.append( " on empty" );
|
||||
}
|
||||
}
|
||||
sb.append( ')' );
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* @since 7.0
|
||||
*/
|
||||
public enum JsonQueryEmptyBehavior implements SqlAstNode {
|
||||
ERROR,
|
||||
NULL,
|
||||
EMPTY_ARRAY,
|
||||
EMPTY_OBJECT;
|
||||
|
||||
@Override
|
||||
public void accept(SqlAstWalker sqlTreeWalker) {
|
||||
throw new UnsupportedOperationException("JsonQueryEmptyBehavior doesn't support walking");
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* @since 7.0
|
||||
*/
|
||||
public enum JsonQueryErrorBehavior implements SqlAstNode {
|
||||
ERROR,
|
||||
NULL,
|
||||
EMPTY_ARRAY,
|
||||
EMPTY_OBJECT;
|
||||
|
||||
@Override
|
||||
public void accept(SqlAstWalker sqlTreeWalker) {
|
||||
throw new UnsupportedOperationException("JsonQueryErrorBehavior doesn't support walking");
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* @since 7.0
|
||||
*/
|
||||
public enum JsonQueryWrapMode implements SqlAstNode {
|
||||
WITH_WRAPPER,
|
||||
WITHOUT_WRAPPER,
|
||||
WITH_CONDITIONAL_WRAPPER;
|
||||
|
||||
@Override
|
||||
public void accept(SqlAstWalker sqlTreeWalker) {
|
||||
throw new UnsupportedOperationException("JsonQueryWrapMode doesn't support walking");
|
||||
}
|
||||
|
||||
}
|
|
@ -25,7 +25,6 @@ import org.hibernate.dialect.H2Dialect;
|
|||
import org.hibernate.testing.orm.junit.EntityManagerFactoryScope;
|
||||
import org.hibernate.testing.orm.junit.Jpa;
|
||||
import org.hibernate.testing.orm.junit.RequiresDialect;
|
||||
import org.hibernate.testing.orm.junit.SkipForDialect;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
|
@ -41,7 +40,6 @@ import static org.junit.Assert.assertNotNull;
|
|||
FetchingTest.Project.class
|
||||
})
|
||||
@RequiresDialect(H2Dialect.class)
|
||||
@SkipForDialect(dialectClass = H2Dialect.class, majorVersion = 2, matchSubTypes = true, reason = "See https://github.com/h2database/h2database/issues/3338")
|
||||
public class FetchingTest {
|
||||
|
||||
@Test
|
||||
|
@ -168,17 +166,11 @@ public class FetchingTest {
|
|||
@NaturalId
|
||||
private String username;
|
||||
|
||||
@Column(name = "pswd")
|
||||
@Column(name = "pswd", columnDefinition = "varbinary")
|
||||
@ColumnTransformer(
|
||||
read = "decrypt('AES', '00', pswd )",
|
||||
read = "trim(trailing u&'\\0000' from cast(decrypt('AES', '00', pswd ) as character varying))",
|
||||
write = "encrypt('AES', '00', ?)"
|
||||
)
|
||||
// For H2 2.0.202+ one must use the varbinary DDL type
|
||||
// @Column(name = "pswd", columnDefinition = "varbinary")
|
||||
// @ColumnTransformer(
|
||||
// read = "trim(trailing u&'\\0000' from cast(decrypt('AES', '00', pswd ) as character varying))",
|
||||
// write = "encrypt('AES', '00', ?)"
|
||||
// )
|
||||
private String password;
|
||||
|
||||
private int accessLevel;
|
||||
|
|
|
@ -0,0 +1,134 @@
|
|||
/*
|
||||
* 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.SupportsJsonQuery.class)
|
||||
public class JsonQueryTest {
|
||||
|
||||
@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-query-example[]
|
||||
List<Tuple> results = em.createQuery( "select json_query(e.json, '$.theString') from EntityWithJson e", Tuple.class )
|
||||
.getResultList();
|
||||
//end::hql-json-query-example[]
|
||||
assertEquals( 1, results.size() );
|
||||
} );
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPassing(SessionFactoryScope scope) {
|
||||
scope.inSession( em -> {
|
||||
//tag::hql-json-query-passing-example[]
|
||||
List<Tuple> results = em.createQuery( "select json_query(e.json, '$.theArray[$idx]' passing 1 as idx) from EntityWithJson e", Tuple.class )
|
||||
.getResultList();
|
||||
//end::hql-json-query-passing-example[]
|
||||
assertEquals( 1, results.size() );
|
||||
} );
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWithWrapper(SessionFactoryScope scope) {
|
||||
scope.inSession( em -> {
|
||||
//tag::hql-json-query-with-wrapper-example[]
|
||||
List<Tuple> results = em.createQuery( "select json_query(e.json, '$.theInt' with wrapper) from EntityWithJson e", Tuple.class )
|
||||
.getResultList();
|
||||
//end::hql-json-query-with-wrapper-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-query-on-error-example[]
|
||||
em.createQuery( "select json_query('invalidJson', '$.theInt' error on error) from EntityWithJson e")
|
||||
.getResultList();
|
||||
//end::hql-json-query-on-error-example[]
|
||||
fail("error clause should fail because of invalid json document");
|
||||
}
|
||||
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-query-on-empty-example[]
|
||||
em.createQuery("select json_query(e.json, '$.nonExisting' error on empty error on error) from EntityWithJson e" )
|
||||
.getResultList();
|
||||
//end::hql-json-query-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;
|
||||
}
|
||||
}
|
||||
} );
|
||||
}
|
||||
|
||||
}
|
|
@ -34,7 +34,6 @@ import static org.junit.Assert.assertEquals;
|
|||
@SkipForDialect(dialectClass = HSQLDialect.class)
|
||||
@SkipForDialect(dialectClass = DerbyDialect.class)
|
||||
@SkipForDialect(dialectClass = SybaseASEDialect.class)
|
||||
@SkipForDialect(dialectClass = PostgreSQLDialect.class, majorVersion = 10, matchSubTypes = true)
|
||||
@SkipForDialect(dialectClass = PostgreSQLDialect.class, majorVersion = 11, matchSubTypes = true) // 'generated always' was added in 12
|
||||
@SkipForDialect(dialectClass = AltibaseDialect.class, reason = "generated always is not supported in Altibase")
|
||||
public class GeneratedAlwaysTest {
|
||||
|
|
|
@ -6,12 +6,15 @@
|
|||
*/
|
||||
package org.hibernate.orm.test.query.hql;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.hibernate.annotations.JdbcTypeCode;
|
||||
import org.hibernate.dialect.HSQLDialect;
|
||||
import org.hibernate.dialect.OracleDialect;
|
||||
import org.hibernate.type.SqlTypes;
|
||||
|
||||
import org.hibernate.testing.orm.junit.DialectContext;
|
||||
|
@ -21,11 +24,21 @@ 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.hibernate.testing.orm.junit.SkipForDialect;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.node.ArrayNode;
|
||||
import com.fasterxml.jackson.databind.node.BinaryNode;
|
||||
import com.fasterxml.jackson.databind.node.BooleanNode;
|
||||
import com.fasterxml.jackson.databind.node.NullNode;
|
||||
import com.fasterxml.jackson.databind.node.NumericNode;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import com.fasterxml.jackson.databind.node.TextNode;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.Tuple;
|
||||
|
@ -44,7 +57,7 @@ public class JsonFunctionTests {
|
|||
|
||||
JsonHolder entity;
|
||||
|
||||
@BeforeAll
|
||||
@BeforeEach
|
||||
public void prepareData(SessionFactoryScope scope) {
|
||||
scope.inTransaction(
|
||||
em -> {
|
||||
|
@ -58,11 +71,26 @@ public class JsonFunctionTests {
|
|||
entity.json.put( "theNull", null );
|
||||
entity.json.put( "theArray", new String[] { "a", "b", "c" } );
|
||||
entity.json.put( "theObject", new HashMap<>( entity.json ) );
|
||||
entity.json.put(
|
||||
"theNestedObjects",
|
||||
List.of(
|
||||
Map.of( "id", 1, "name", "val1" ),
|
||||
Map.of( "id", 2, "name", "val2" ),
|
||||
Map.of( "id", 3, "name", "val3" )
|
||||
)
|
||||
);
|
||||
em.persist(entity);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
public void cleanupData(SessionFactoryScope scope) {
|
||||
scope.inTransaction(
|
||||
em -> em.createMutationQuery( "delete from JsonHolder" ).executeUpdate()
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsJsonValue.class)
|
||||
public void testJsonValue(SessionFactoryScope scope) {
|
||||
|
@ -114,6 +142,48 @@ public class JsonFunctionTests {
|
|||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsJsonQuery.class)
|
||||
public void testJsonQuery(SessionFactoryScope scope) {
|
||||
scope.inTransaction(
|
||||
session -> {
|
||||
Tuple tuple = session.createQuery(
|
||||
"select " +
|
||||
"json_query(e.json, '$.theArray'), " +
|
||||
"json_query(e.json, '$.theNestedObjects'), " +
|
||||
"json_query(e.json, '$.theNestedObjects[$idx]' passing :idx as idx with wrapper) " +
|
||||
"from JsonHolder e " +
|
||||
"where e.id = 1L",
|
||||
Tuple.class
|
||||
).setParameter( "idx", 0 ).getSingleResult();
|
||||
assertEquals( parseJson( "[\"a\",\"b\",\"c\"]" ), parseJson( tuple.get( 0, String.class ) ) );
|
||||
assertEquals(
|
||||
parseJson(
|
||||
"[{\"id\":1,\"name\":\"val1\"},{\"id\":2,\"name\":\"val2\"},{\"id\":3,\"name\":\"val3\"}]" ),
|
||||
parseJson( tuple.get( 1, String.class ) )
|
||||
);
|
||||
assertEquals( parseJson( "[{\"id\":1,\"name\":\"val1\"}]" ), parseJson( tuple.get( 2, String.class ) ) );
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsJsonQueryNestedPath.class)
|
||||
public void testJsonQueryNested(SessionFactoryScope scope) {
|
||||
scope.inTransaction(
|
||||
session -> {
|
||||
Tuple tuple = session.createQuery(
|
||||
"select " +
|
||||
"json_query(e.json, '$.theNestedObjects[*].id' with wrapper) " +
|
||||
"from JsonHolder e " +
|
||||
"where e.id = 1L",
|
||||
Tuple.class
|
||||
).getSingleResult();
|
||||
assertEquals( parseJson( "[1,2,3]" ), parseJson( tuple.get( 0, String.class ) ) );
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsJsonArray.class)
|
||||
public void testJsonArray(SessionFactoryScope scope) {
|
||||
|
@ -224,6 +294,7 @@ public class JsonFunctionTests {
|
|||
|
||||
@Test
|
||||
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsJsonExists.class)
|
||||
@SkipForDialect(dialectClass = OracleDialect.class, majorVersion = 21, matchSubTypes = true, reason = "Oracle bug in versions before 23")
|
||||
public void testJsonExists(SessionFactoryScope scope) {
|
||||
scope.inTransaction(
|
||||
session -> {
|
||||
|
@ -266,6 +337,52 @@ public class JsonFunctionTests {
|
|||
}
|
||||
}
|
||||
|
||||
private static Object parseJson(String json) {
|
||||
try {
|
||||
return toJavaNode( MAPPER.readTree( json ) );
|
||||
}
|
||||
catch (JsonProcessingException e) {
|
||||
throw new RuntimeException( e );
|
||||
}
|
||||
}
|
||||
|
||||
private static Object toJavaNode(JsonNode jsonNode) {
|
||||
if ( jsonNode instanceof ArrayNode arrayNode ) {
|
||||
final var list = new ArrayList<>( arrayNode.size() );
|
||||
for ( JsonNode node : arrayNode ) {
|
||||
list.add( toJavaNode( node ) );
|
||||
}
|
||||
return list;
|
||||
}
|
||||
else if ( jsonNode instanceof ObjectNode object ) {
|
||||
final var map = new HashMap<>( object.size() );
|
||||
final Iterator<Map.Entry<String, JsonNode>> iter = object.fields();
|
||||
while ( iter.hasNext() ) {
|
||||
final Map.Entry<String, JsonNode> entry = iter.next();
|
||||
map.put( entry.getKey(), toJavaNode( entry.getValue() ) );
|
||||
}
|
||||
return map;
|
||||
}
|
||||
else if ( jsonNode instanceof NullNode ) {
|
||||
return null;
|
||||
}
|
||||
else if ( jsonNode instanceof NumericNode numericNode ) {
|
||||
return numericNode.numberValue();
|
||||
}
|
||||
else if ( jsonNode instanceof BooleanNode booleanNode ) {
|
||||
return booleanNode.booleanValue();
|
||||
}
|
||||
else if ( jsonNode instanceof TextNode textNode ) {
|
||||
return textNode.textValue();
|
||||
}
|
||||
else if ( jsonNode instanceof BinaryNode binaryNode ) {
|
||||
return binaryNode.binaryValue();
|
||||
}
|
||||
else {
|
||||
throw new UnsupportedOperationException( "Unsupported node type: " + jsonNode.getClass().getName() );
|
||||
}
|
||||
}
|
||||
|
||||
@Entity(name = "JsonHolder")
|
||||
public static class JsonHolder {
|
||||
@Id
|
||||
|
|
|
@ -322,6 +322,8 @@ public class CustomRunner extends BlockJUnit4ClassRunner {
|
|||
effectiveSkipForDialect.microVersion(),
|
||||
dialect,
|
||||
effectiveSkipForDialect.matchSubTypes()
|
||||
? DialectFilterExtension.VersionMatchMode.SAME_OR_OLDER
|
||||
: DialectFilterExtension.VersionMatchMode.SAME
|
||||
);
|
||||
|
||||
if ( versionsMatch ) {
|
||||
|
@ -474,6 +476,8 @@ public class CustomRunner extends BlockJUnit4ClassRunner {
|
|||
matchingMicroVersion,
|
||||
dialect,
|
||||
requiresDialect.matchSubTypes()
|
||||
? DialectFilterExtension.VersionMatchMode.SAME_OR_NEWER
|
||||
: DialectFilterExtension.VersionMatchMode.SAME
|
||||
);
|
||||
}
|
||||
else {
|
||||
|
|
|
@ -732,6 +732,21 @@ abstract public class DialectFeatureChecks {
|
|||
}
|
||||
}
|
||||
|
||||
public static class SupportsJsonQuery implements DialectFeatureCheck {
|
||||
public boolean apply(Dialect dialect) {
|
||||
return definesFunction( dialect, "json_query" );
|
||||
}
|
||||
}
|
||||
|
||||
public static class SupportsJsonQueryNestedPath implements DialectFeatureCheck {
|
||||
public boolean apply(Dialect dialect) {
|
||||
return definesFunction( dialect, "json_query" )
|
||||
&& !( dialect instanceof SQLServerDialect )
|
||||
&& !( dialect instanceof H2Dialect )
|
||||
&& !( dialect instanceof CockroachDialect );
|
||||
}
|
||||
}
|
||||
|
||||
public static class SupportsJsonExists implements DialectFeatureCheck {
|
||||
public boolean apply(Dialect dialect) {
|
||||
return definesFunction( dialect, "json_exists" );
|
||||
|
|
|
@ -84,6 +84,8 @@ public class DialectFilterExtension implements ExecutionCondition {
|
|||
matchingMicroVersion,
|
||||
dialect,
|
||||
requiresDialect.matchSubTypes()
|
||||
? VersionMatchMode.SAME_OR_NEWER
|
||||
: VersionMatchMode.SAME
|
||||
);
|
||||
}
|
||||
else {
|
||||
|
@ -136,6 +138,21 @@ public class DialectFilterExtension implements ExecutionCondition {
|
|||
int matchingMicroVersion,
|
||||
Dialect dialect,
|
||||
boolean matchNewerVersions) {
|
||||
return versionsMatch(
|
||||
matchingMajorVersion,
|
||||
matchingMinorVersion,
|
||||
matchingMicroVersion,
|
||||
dialect,
|
||||
matchNewerVersions ? VersionMatchMode.SAME_OR_NEWER : VersionMatchMode.SAME
|
||||
);
|
||||
}
|
||||
|
||||
public static boolean versionsMatch(
|
||||
int matchingMajorVersion,
|
||||
int matchingMinorVersion,
|
||||
int matchingMicroVersion,
|
||||
Dialect dialect,
|
||||
VersionMatchMode matchMode) {
|
||||
if ( matchingMajorVersion < 0 ) {
|
||||
return false;
|
||||
}
|
||||
|
@ -148,12 +165,20 @@ public class DialectFilterExtension implements ExecutionCondition {
|
|||
matchingMicroVersion = 0;
|
||||
}
|
||||
|
||||
if ( matchNewerVersions ) {
|
||||
if ( matchMode == VersionMatchMode.SAME_OR_NEWER ) {
|
||||
return dialect.getVersion().isSameOrAfter( matchingMajorVersion, matchingMinorVersion, matchingMicroVersion );
|
||||
}
|
||||
else {
|
||||
return dialect.getVersion().isSame( matchingMajorVersion );
|
||||
if ( matchMode == VersionMatchMode.SAME_OR_OLDER
|
||||
&& dialect.getVersion().isBefore( matchingMajorVersion, matchingMinorVersion, matchingMicroVersion ) ) {
|
||||
return true;
|
||||
}
|
||||
return dialect.getVersion().isSame( matchingMajorVersion );
|
||||
}
|
||||
|
||||
public enum VersionMatchMode {
|
||||
SAME,
|
||||
SAME_OR_NEWER,
|
||||
SAME_OR_OLDER
|
||||
}
|
||||
|
||||
private ConditionEvaluationResult evaluateSkipConditions(ExtensionContext context, Dialect dialect, String enabledResult) {
|
||||
|
@ -194,6 +219,8 @@ public class DialectFilterExtension implements ExecutionCondition {
|
|||
effectiveSkipForDialect.microVersion(),
|
||||
dialect,
|
||||
effectiveSkipForDialect.matchSubTypes()
|
||||
? VersionMatchMode.SAME_OR_OLDER
|
||||
: VersionMatchMode.SAME
|
||||
);
|
||||
|
||||
if ( versionsMatch ) {
|
||||
|
|
Loading…
Reference in New Issue