HHH-18496 Add json_exists and support the passing clause
This commit is contained in:
parent
016b463973
commit
6454aaf055
|
@ -1632,6 +1632,7 @@ The following functions deal with SQL JSON types, which are not supported on eve
|
|||
| `json_object()` | Constructs a JSON object from pairs of key and value arguments
|
||||
| `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
|
||||
|===
|
||||
|
||||
|
||||
|
@ -1713,7 +1714,8 @@ The first argument is an expression to a JSON document. The second argument is a
|
|||
|
||||
WARNING: Some databases might also return non-scalar values. Beware that this behavior is not portable.
|
||||
|
||||
NOTE: It is recommended to only us the dot notation for JSON paths, since most databases support only that.
|
||||
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-value-example]]
|
||||
====
|
||||
|
@ -1723,6 +1725,16 @@ include::{json-example-dir-hql}/JsonValueTest.java[tags=hql-json-value-example]
|
|||
----
|
||||
====
|
||||
|
||||
The `passing` clause allows to reuse the same JSON path but pass different values for evaluation.
|
||||
|
||||
[[hql-json-value-passing-example]]
|
||||
====
|
||||
[source, java, indent=0]
|
||||
----
|
||||
include::{json-example-dir-hql}/JsonValueTest.java[tags=hql-json-value-passing-example]
|
||||
----
|
||||
====
|
||||
|
||||
The `returning` clause allows to specify the <<hql-function-cast,cast target>> i.e. the type of value to extract.
|
||||
|
||||
[[hql-json-value-returning-example]]
|
||||
|
@ -1733,17 +1745,6 @@ include::{json-example-dir-hql}/JsonValueTest.java[tags=hql-json-value-returning
|
|||
----
|
||||
====
|
||||
|
||||
The `on empty` clause defines the behavior when the JSON path does not match the JSON document.
|
||||
By default, `null` is returned on empty.
|
||||
|
||||
[[hql-json-value-on-error-example]]
|
||||
====
|
||||
[source, java, indent=0]
|
||||
----
|
||||
include::{json-example-dir-hql}/JsonValueTest.java[tags=hql-json-value-on-error-example]
|
||||
----
|
||||
====
|
||||
|
||||
The `on error` clause defines the behavior when an error occurs while resolving the value for the JSON path.
|
||||
Conditions that classify as errors are database dependent, but usual errors which can be handled with this clause are:
|
||||
|
||||
|
@ -1754,6 +1755,17 @@ Conditions that classify as errors are database dependent, but usual errors whic
|
|||
The default behavior of `on error` is database specific, but usually, `null` is returned on an error.
|
||||
It is recommended to specify this clause when the exact error behavior is important.
|
||||
|
||||
[[hql-json-value-on-error-example]]
|
||||
====
|
||||
[source, java, indent=0]
|
||||
----
|
||||
include::{json-example-dir-hql}/JsonValueTest.java[tags=hql-json-value-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-value-on-empty-example]]
|
||||
====
|
||||
[source, java, indent=0]
|
||||
|
@ -1767,6 +1779,59 @@ Depending on the database, an error might still be thrown even without that, but
|
|||
|
||||
NOTE: The H2 emulation only supports absolute JSON paths using the dot notation.
|
||||
|
||||
[[hql-json-exists-function]]
|
||||
===== `json_exists()`
|
||||
|
||||
Checks if a JSON document contains a https://www.ietf.org/archive/id/draft-goessner-dispatch-jsonpath-00.html[JSON path].
|
||||
|
||||
[[hql-json-exists-bnf]]
|
||||
[source, antlrv4, indent=0]
|
||||
----
|
||||
include::{extrasdir}/json_exists_bnf.txt[]
|
||||
----
|
||||
|
||||
The first argument is an expression to a JSON document. The second argument is a JSON path as String expression.
|
||||
|
||||
NOTE: It is recommended to only us the dot notation for JSON paths instead of the bracket notation,
|
||||
since most databases support only that.
|
||||
|
||||
[[hql-json-exists-example]]
|
||||
====
|
||||
[source, java, indent=0]
|
||||
----
|
||||
include::{json-example-dir-hql}/JsonExistsTest.java[tags=hql-json-exists-example]
|
||||
----
|
||||
====
|
||||
|
||||
The `passing` clause allows to reuse the same JSON path but pass different values for evaluation.
|
||||
|
||||
[[hql-json-exists-passing-example]]
|
||||
====
|
||||
[source, java, indent=0]
|
||||
----
|
||||
include::{json-example-dir-hql}/JsonExistsTest.java[tags=hql-json-exists-passing-example]
|
||||
----
|
||||
====
|
||||
|
||||
The `on error` clause defines the behavior when an error occurs while checking for existence 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
|
||||
|
||||
The default behavior of `on error` is database specific, but usually, `false` is returned on an error.
|
||||
It is recommended to specify this clause when the exact error behavior is important.
|
||||
|
||||
[[hql-json-exists-on-error-example]]
|
||||
====
|
||||
[source, java, indent=0]
|
||||
----
|
||||
include::{json-example-dir-hql}/JsonExistsTest.java[tags=hql-json-exists-on-error-example]
|
||||
----
|
||||
====
|
||||
|
||||
NOTE: The H2 emulation only supports absolute JSON paths using the dot notation.
|
||||
|
||||
[[hql-user-defined-functions]]
|
||||
==== Native and user-defined functions
|
||||
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
"json_exists(" expression, expression passingClause? onErrorClause? ")"
|
||||
|
||||
passingClause
|
||||
: "passing" expression "as" identifier ("," expression "as" identifier)*
|
||||
|
||||
onErrorClause
|
||||
: ( "error" | "true" | "false" ) "on error";
|
|
@ -1,4 +1,7 @@
|
|||
"json_value(" expression, expression ("returning" castTarget)? onErrorClause? onEmptyClause? ")"
|
||||
"json_value(" expression, expression passingClause? ("returning" castTarget)? onErrorClause? onEmptyClause? ")"
|
||||
|
||||
passingClause
|
||||
: "passing" expression "as" identifier ("," expression "as" identifier)*
|
||||
|
||||
onErrorClause
|
||||
: ( "error" | "null" | ( "default" expression ) ) "on error";
|
||||
|
|
|
@ -503,6 +503,7 @@ public class CockroachLegacyDialect extends Dialect {
|
|||
|
||||
functionFactory.jsonValue_cockroachdb();
|
||||
functionFactory.jsonObject_postgresql();
|
||||
functionFactory.jsonExists_postgresql();
|
||||
functionFactory.jsonArray_postgresql();
|
||||
|
||||
// Postgres uses # instead of ^ for XOR
|
||||
|
|
|
@ -431,7 +431,8 @@ public class DB2LegacyDialect extends Dialect {
|
|||
functionFactory.listagg( null );
|
||||
|
||||
if ( getDB2Version().isSameOrAfter( 11 ) ) {
|
||||
functionFactory.jsonValue();
|
||||
functionFactory.jsonValue_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.jsonExists_h2();
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
|
|
@ -654,6 +654,7 @@ public class MySQLLegacyDialect extends Dialect {
|
|||
|
||||
if ( getMySQLVersion().isSameOrAfter( 5, 7 ) ) {
|
||||
functionFactory.jsonValue_mysql();
|
||||
functionFactory.jsonExists_mysql();
|
||||
functionFactory.jsonObject_mysql();
|
||||
functionFactory.jsonArray_mysql();
|
||||
}
|
||||
|
|
|
@ -322,7 +322,8 @@ public class OracleLegacyDialect extends Dialect {
|
|||
functionFactory.arrayToString_oracle();
|
||||
|
||||
if ( getVersion().isSameOrAfter( 12 ) ) {
|
||||
functionFactory.jsonValue_literal_path();
|
||||
functionFactory.jsonValue_oracle();
|
||||
functionFactory.jsonExists_oracle();
|
||||
functionFactory.jsonObject_oracle();
|
||||
functionFactory.jsonArray_oracle();
|
||||
}
|
||||
|
|
|
@ -634,11 +634,13 @@ public class PostgreSQLLegacyDialect extends Dialect {
|
|||
|
||||
if ( getVersion().isSameOrAfter( 17 ) ) {
|
||||
functionFactory.jsonValue();
|
||||
functionFactory.jsonExists();
|
||||
functionFactory.jsonObject();
|
||||
functionFactory.jsonArray();
|
||||
}
|
||||
else {
|
||||
functionFactory.jsonValue_postgresql();
|
||||
functionFactory.jsonExists_postgresql();
|
||||
if ( getVersion().isSameOrAfter( 16 ) ) {
|
||||
functionFactory.jsonObject();
|
||||
functionFactory.jsonArray();
|
||||
|
|
|
@ -402,6 +402,7 @@ public class SQLServerLegacyDialect extends AbstractTransactSQLDialect {
|
|||
functionFactory.hypotheticalOrderedSetAggregates_windowEmulation();
|
||||
if ( getVersion().isSameOrAfter( 13 ) ) {
|
||||
functionFactory.jsonValue_sqlserver();
|
||||
functionFactory.jsonExists_sqlserver();
|
||||
functionFactory.jsonObject_sqlserver();
|
||||
functionFactory.jsonArray_sqlserver();
|
||||
}
|
||||
|
|
|
@ -223,6 +223,7 @@ INTO : [iI] [nN] [tT] [oO];
|
|||
IS : [iI] [sS];
|
||||
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_VALUE : [jJ] [sS] [oO] [nN] '_' [vV] [aA] [lL] [uU] [eE];
|
||||
KEY : [kK] [eE] [yY];
|
||||
|
@ -274,6 +275,7 @@ OVERFLOW : [oO] [vV] [eE] [rR] [fF] [lL] [oO] [wW];
|
|||
OVERLAY : [oO] [vV] [eE] [rR] [lL] [aA] [yY];
|
||||
PAD : [pP] [aA] [dD];
|
||||
PARTITION : [pP] [aA] [rR] [tT] [iI] [tT] [iI] [oO] [nN];
|
||||
PASSING : [pP] [aA] [sS] [sS] [iI] [nN] [gG];
|
||||
PERCENT : [pP] [eE] [rR] [cC] [eE] [nN] [tT];
|
||||
PLACING : [pP] [lL] [aA] [cC] [iI] [nN] [gG];
|
||||
POSITION : [pP] [oO] [sS] [iI] [tT] [iI] [oO] [nN];
|
||||
|
|
|
@ -1622,16 +1622,21 @@ rollup
|
|||
;
|
||||
|
||||
jsonFunction
|
||||
: jsonValueFunction
|
||||
| jsonArrayFunction
|
||||
: jsonArrayFunction
|
||||
| jsonExistsFunction
|
||||
| jsonObjectFunction
|
||||
| jsonValueFunction
|
||||
;
|
||||
|
||||
/**
|
||||
* The 'json_value()' function
|
||||
*/
|
||||
jsonValueFunction
|
||||
: JSON_VALUE LEFT_PAREN expression COMMA expression jsonValueReturningClause? jsonValueOnErrorOrEmptyClause? jsonValueOnErrorOrEmptyClause? RIGHT_PAREN
|
||||
: JSON_VALUE LEFT_PAREN expression COMMA expression jsonPassingClause? jsonValueReturningClause? jsonValueOnErrorOrEmptyClause? jsonValueOnErrorOrEmptyClause? RIGHT_PAREN
|
||||
;
|
||||
|
||||
jsonPassingClause
|
||||
: PASSING expressionOrPredicate AS identifier (COMMA expressionOrPredicate AS identifier)*
|
||||
;
|
||||
|
||||
jsonValueReturningClause
|
||||
|
@ -1641,6 +1646,16 @@ jsonValueReturningClause
|
|||
jsonValueOnErrorOrEmptyClause
|
||||
: ( ERROR | NULL | ( DEFAULT expression ) ) ON (ERROR|EMPTY);
|
||||
|
||||
/**
|
||||
* The 'json_exists()' function
|
||||
*/
|
||||
jsonExistsFunction
|
||||
: JSON_EXISTS LEFT_PAREN expression COMMA expression jsonPassingClause? jsonExistsOnErrorClause? RIGHT_PAREN
|
||||
;
|
||||
|
||||
jsonExistsOnErrorClause
|
||||
: ( ERROR | TRUE | FALSE ) ON ERROR;
|
||||
|
||||
/**
|
||||
* The 'json_array()' function
|
||||
*/
|
||||
|
@ -1760,6 +1775,7 @@ jsonNullClause
|
|||
| IS
|
||||
| JOIN
|
||||
| JSON_ARRAY
|
||||
| JSON_EXISTS
|
||||
| JSON_OBJECT
|
||||
| JSON_VALUE
|
||||
| KEY
|
||||
|
@ -1812,6 +1828,7 @@ jsonNullClause
|
|||
| OVERLAY
|
||||
| PAD
|
||||
| PARTITION
|
||||
| PASSING
|
||||
| PERCENT
|
||||
| PLACING
|
||||
| POSITION
|
||||
|
|
|
@ -469,6 +469,7 @@ public class CockroachDialect extends Dialect {
|
|||
functionFactory.arrayToString_postgresql();
|
||||
|
||||
functionFactory.jsonValue_cockroachdb();
|
||||
functionFactory.jsonExists_postgresql();
|
||||
functionFactory.jsonObject_postgresql();
|
||||
functionFactory.jsonArray_postgresql();
|
||||
|
||||
|
|
|
@ -417,7 +417,8 @@ public class DB2Dialect extends Dialect {
|
|||
functionFactory.listagg( null );
|
||||
|
||||
if ( getDB2Version().isSameOrAfter( 11 ) ) {
|
||||
functionFactory.jsonValue();
|
||||
functionFactory.jsonValue_no_passing();
|
||||
functionFactory.jsonExists_no_passing();
|
||||
functionFactory.jsonObject_db2();
|
||||
functionFactory.jsonArray_db2();
|
||||
}
|
||||
|
|
|
@ -4692,15 +4692,7 @@ public abstract class Dialect implements ConversionContext, TypeContributor, Fun
|
|||
* @apiNote Needed because MySQL has nonstandard escape characters
|
||||
*/
|
||||
public void appendLiteral(SqlAppender appender, String literal) {
|
||||
appender.appendSql( '\'' );
|
||||
for ( int i = 0; i < literal.length(); i++ ) {
|
||||
final char c = literal.charAt( i );
|
||||
if ( c == '\'' ) {
|
||||
appender.appendSql( '\'' );
|
||||
}
|
||||
appender.appendSql( c );
|
||||
}
|
||||
appender.appendSql( '\'' );
|
||||
appender.appendSingleQuoteEscapedString( literal );
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -347,6 +347,7 @@ public class H2Dialect extends Dialect {
|
|||
functionFactory.jsonArray();
|
||||
if ( getVersion().isSameOrAfter( 2, 2, 220 ) ) {
|
||||
functionFactory.jsonValue_h2();
|
||||
functionFactory.jsonExists_h2();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -491,7 +491,8 @@ public class HANADialect extends Dialect {
|
|||
|
||||
if ( getVersion().isSameOrAfter(2, 0, 20) ) {
|
||||
// Introduced in 2.0 SPS 02
|
||||
functionFactory.jsonValue();
|
||||
functionFactory.jsonValue_no_passing();
|
||||
functionFactory.jsonExists_hana();
|
||||
if ( getVersion().isSameOrAfter(2, 0, 40) ) {
|
||||
// Introduced in 2.0 SPS 04
|
||||
functionFactory.jsonObject_hana();
|
||||
|
|
|
@ -639,6 +639,7 @@ public class MySQLDialect extends Dialect {
|
|||
functionFactory.listagg_groupConcat();
|
||||
|
||||
functionFactory.jsonValue_mysql();
|
||||
functionFactory.jsonExists_mysql();
|
||||
functionFactory.jsonObject_mysql();
|
||||
functionFactory.jsonArray_mysql();
|
||||
}
|
||||
|
|
|
@ -399,7 +399,8 @@ public class OracleDialect extends Dialect {
|
|||
functionFactory.arrayFill_oracle();
|
||||
functionFactory.arrayToString_oracle();
|
||||
|
||||
functionFactory.jsonValue_literal_path();
|
||||
functionFactory.jsonValue_oracle();
|
||||
functionFactory.jsonExists_oracle();
|
||||
functionFactory.jsonObject_oracle();
|
||||
functionFactory.jsonArray_oracle();
|
||||
}
|
||||
|
|
|
@ -595,11 +595,13 @@ public class PostgreSQLDialect extends Dialect {
|
|||
|
||||
if ( getVersion().isSameOrAfter( 17 ) ) {
|
||||
functionFactory.jsonValue();
|
||||
functionFactory.jsonExists();
|
||||
functionFactory.jsonObject();
|
||||
functionFactory.jsonArray();
|
||||
}
|
||||
else {
|
||||
functionFactory.jsonValue_postgresql();
|
||||
functionFactory.jsonExists_postgresql();
|
||||
if ( getVersion().isSameOrAfter( 16 ) ) {
|
||||
functionFactory.jsonObject();
|
||||
functionFactory.jsonArray();
|
||||
|
|
|
@ -420,6 +420,7 @@ public class SQLServerDialect extends AbstractTransactSQLDialect {
|
|||
functionFactory.hypotheticalOrderedSetAggregates_windowEmulation();
|
||||
if ( getVersion().isSameOrAfter( 13 ) ) {
|
||||
functionFactory.jsonValue_sqlserver();
|
||||
functionFactory.jsonExists_sqlserver();
|
||||
functionFactory.jsonObject_sqlserver();
|
||||
functionFactory.jsonArray_sqlserver();
|
||||
}
|
||||
|
|
|
@ -79,25 +79,31 @@ import org.hibernate.dialect.function.array.PostgreSQLArrayTrimEmulation;
|
|||
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.H2JsonValueFunction;
|
||||
import org.hibernate.dialect.function.json.HANAJsonArrayFunction;
|
||||
import org.hibernate.dialect.function.json.HANAJsonExistsFunction;
|
||||
import org.hibernate.dialect.function.json.HANAJsonObjectFunction;
|
||||
import org.hibernate.dialect.function.json.HSQLJsonArrayFunction;
|
||||
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.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.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.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.SQLServerJsonValueFunction;
|
||||
import org.hibernate.query.sqm.function.SqmFunctionRegistry;
|
||||
|
@ -3349,14 +3355,21 @@ public class CommonFunctionFactory {
|
|||
* json_value() function
|
||||
*/
|
||||
public void jsonValue() {
|
||||
functionRegistry.register( "json_value", new JsonValueFunction( typeConfiguration, true ) );
|
||||
functionRegistry.register( "json_value", new JsonValueFunction( typeConfiguration, true, true ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* json_value() function that supports only literal json paths
|
||||
* json_value() function that doesn't support the passing clause
|
||||
*/
|
||||
public void jsonValue_literal_path() {
|
||||
functionRegistry.register( "json_value", new JsonValueFunction( typeConfiguration, false ) );
|
||||
public void jsonValue_no_passing() {
|
||||
functionRegistry.register( "json_value", new JsonValueFunction( typeConfiguration, true, false ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Oracle json_value() function
|
||||
*/
|
||||
public void jsonValue_oracle() {
|
||||
functionRegistry.register( "json_value", new JsonValueFunction( typeConfiguration, false, false ) );
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -3401,6 +3414,62 @@ public class CommonFunctionFactory {
|
|||
functionRegistry.register( "json_value", new H2JsonValueFunction( typeConfiguration ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* json_exists() function
|
||||
*/
|
||||
public void jsonExists() {
|
||||
functionRegistry.register( "json_exists", new JsonExistsFunction( typeConfiguration, true, true ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* json_exists() function that doesn't support the passing clause
|
||||
*/
|
||||
public void jsonExists_no_passing() {
|
||||
functionRegistry.register( "json_exists", new JsonExistsFunction( typeConfiguration, true, false ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Oracle json_exists() function
|
||||
*/
|
||||
public void jsonExists_oracle() {
|
||||
functionRegistry.register( "json_exists", new JsonExistsFunction( typeConfiguration, false, true ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* H2 json_exists() function
|
||||
*/
|
||||
public void jsonExists_h2() {
|
||||
functionRegistry.register( "json_exists", new H2JsonExistsFunction( typeConfiguration ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* SQL Server json_exists() function
|
||||
*/
|
||||
public void jsonExists_sqlserver() {
|
||||
functionRegistry.register( "json_exists", new SQLServerJsonExistsFunction( typeConfiguration ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* PostgreSQL json_exists() function
|
||||
*/
|
||||
public void jsonExists_postgresql() {
|
||||
functionRegistry.register( "json_exists", new PostgreSQLJsonExistsFunction( typeConfiguration ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* MySQL json_exists() function
|
||||
*/
|
||||
public void jsonExists_mysql() {
|
||||
functionRegistry.register( "json_exists", new MySQLJsonExistsFunction( typeConfiguration ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* SAP HANA json_exists() function
|
||||
*/
|
||||
public void jsonExists_hana() {
|
||||
functionRegistry.register( "json_exists", new HANAJsonExistsFunction( typeConfiguration ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* json_object() function
|
||||
*/
|
||||
|
|
|
@ -13,11 +13,11 @@ import org.hibernate.dialect.Dialect;
|
|||
import org.hibernate.query.ReturnableType;
|
||||
import org.hibernate.sql.ast.SqlAstTranslator;
|
||||
import org.hibernate.sql.ast.spi.SqlAppender;
|
||||
import org.hibernate.sql.ast.tree.SqlAstNode;
|
||||
import org.hibernate.sql.ast.tree.expression.Expression;
|
||||
import org.hibernate.sql.ast.tree.expression.JdbcParameter;
|
||||
import org.hibernate.sql.ast.tree.expression.JsonPathPassingClause;
|
||||
import org.hibernate.sql.ast.tree.expression.JsonValueEmptyBehavior;
|
||||
import org.hibernate.sql.ast.tree.expression.JsonValueErrorBehavior;
|
||||
import org.hibernate.sql.ast.tree.expression.Literal;
|
||||
import org.hibernate.type.spi.TypeConfiguration;
|
||||
|
||||
/**
|
||||
|
@ -26,7 +26,7 @@ import org.hibernate.type.spi.TypeConfiguration;
|
|||
public class CockroachDBJsonValueFunction extends JsonValueFunction {
|
||||
|
||||
public CockroachDBJsonValueFunction(TypeConfiguration typeConfiguration) {
|
||||
super( typeConfiguration, true );
|
||||
super( typeConfiguration, true, false );
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -75,6 +75,19 @@ public class CockroachDBJsonValueFunction extends JsonValueFunction {
|
|||
if ( jsonPathElement instanceof JsonPathHelper.JsonAttribute attribute ) {
|
||||
dialect.appendLiteral( sqlAppender, attribute.attribute() );
|
||||
}
|
||||
else if ( jsonPathElement instanceof JsonPathHelper.JsonParameterIndexAccess ) {
|
||||
final JsonPathPassingClause jsonPathPassingClause = arguments.passingClause();
|
||||
assert jsonPathPassingClause != null;
|
||||
final String parameterName = ( (JsonPathHelper.JsonParameterIndexAccess) jsonPathElement ).parameterName();
|
||||
final Expression expression = jsonPathPassingClause.getPassingExpressions().get( parameterName );
|
||||
if ( expression == null ) {
|
||||
throw new QueryException( "JSON path [" + jsonPath + "] uses parameter [" + parameterName + "] that is not passed" );
|
||||
}
|
||||
|
||||
sqlAppender.appendSql( "cast(" );
|
||||
expression.accept( walker );
|
||||
sqlAppender.appendSql( " as text)" );
|
||||
}
|
||||
else {
|
||||
sqlAppender.appendSql( '\'' );
|
||||
sqlAppender.appendSql( ( (JsonPathHelper.JsonIndexAccess) jsonPathElement ).index() );
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
|
||||
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
|
||||
*/
|
||||
package org.hibernate.dialect.function.json;
|
||||
|
||||
import org.hibernate.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.JsonExistsErrorBehavior;
|
||||
import org.hibernate.type.spi.TypeConfiguration;
|
||||
|
||||
/**
|
||||
* H2 json_exists function.
|
||||
*/
|
||||
public class H2JsonExistsFunction extends JsonExistsFunction {
|
||||
|
||||
public H2JsonExistsFunction(TypeConfiguration typeConfiguration) {
|
||||
super( typeConfiguration, true, true );
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void render(
|
||||
SqlAppender sqlAppender,
|
||||
JsonExistsArguments arguments,
|
||||
ReturnableType<?> returnType,
|
||||
SqlAstTranslator<?> walker) {
|
||||
// Json dereference errors by default if the JSON is invalid
|
||||
if ( arguments.errorBehavior() != null && arguments.errorBehavior() != JsonExistsErrorBehavior.ERROR ) {
|
||||
throw new QueryException( "Can't emulate on error clause on H2" );
|
||||
}
|
||||
final String jsonPath;
|
||||
try {
|
||||
jsonPath = walker.getLiteralValue( arguments.jsonPath() );
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new QueryException( "H2 json_value only support literal json paths, but got " + arguments.jsonPath() );
|
||||
}
|
||||
arguments.jsonDocument().accept( walker );
|
||||
sqlAppender.appendSql( " is not null and " );
|
||||
H2JsonValueFunction.renderJsonPath(
|
||||
sqlAppender,
|
||||
arguments.jsonDocument(),
|
||||
arguments.isJsonType(),
|
||||
walker,
|
||||
jsonPath,
|
||||
arguments.passingClause()
|
||||
);
|
||||
sqlAppender.appendSql( " is not null" );
|
||||
}
|
||||
}
|
|
@ -10,21 +10,26 @@ import java.util.List;
|
|||
|
||||
import org.hibernate.QueryException;
|
||||
import org.hibernate.query.ReturnableType;
|
||||
import org.hibernate.query.sqm.sql.internal.BasicValuedPathInterpretation;
|
||||
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.ColumnReference;
|
||||
import org.hibernate.sql.ast.tree.expression.Expression;
|
||||
import org.hibernate.sql.ast.tree.expression.JsonPathPassingClause;
|
||||
import org.hibernate.sql.ast.tree.expression.JsonValueEmptyBehavior;
|
||||
import org.hibernate.sql.ast.tree.expression.JsonValueErrorBehavior;
|
||||
import org.hibernate.type.spi.TypeConfiguration;
|
||||
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
/**
|
||||
* H2 json_value function.
|
||||
*/
|
||||
public class H2JsonValueFunction extends JsonValueFunction {
|
||||
|
||||
public H2JsonValueFunction(TypeConfiguration typeConfiguration) {
|
||||
super( typeConfiguration, false );
|
||||
super( typeConfiguration, false, true );
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -58,7 +63,16 @@ public class H2JsonValueFunction extends JsonValueFunction {
|
|||
if ( defaultExpression != null ) {
|
||||
sqlAppender.appendSql( "coalesce(" );
|
||||
}
|
||||
renderJsonPath( sqlAppender, arguments.jsonDocument(), walker, jsonPath );
|
||||
sqlAppender.appendSql( "cast(" );
|
||||
renderJsonPath(
|
||||
sqlAppender,
|
||||
arguments.jsonDocument(),
|
||||
arguments.isJsonType(),
|
||||
walker,
|
||||
jsonPath,
|
||||
arguments.passingClause()
|
||||
);
|
||||
sqlAppender.appendSql( " as varchar)" );
|
||||
if ( defaultExpression != null ) {
|
||||
sqlAppender.appendSql( ",cast(" );
|
||||
defaultExpression.accept( walker );
|
||||
|
@ -73,27 +87,45 @@ public class H2JsonValueFunction extends JsonValueFunction {
|
|||
}
|
||||
}
|
||||
|
||||
private void renderJsonPath(
|
||||
public static void renderJsonPath(
|
||||
SqlAppender sqlAppender,
|
||||
SqlAstNode jsonDocument,
|
||||
Expression jsonDocument,
|
||||
boolean isJson,
|
||||
SqlAstTranslator<?> walker,
|
||||
String jsonPath) {
|
||||
sqlAppender.appendSql( "cast(" );
|
||||
|
||||
String jsonPath,
|
||||
@Nullable JsonPathPassingClause passingClause) {
|
||||
final List<JsonPathHelper.JsonPathElement> jsonPathElements = JsonPathHelper.parseJsonPathElements( jsonPath );
|
||||
final boolean needsWrapping = jsonPathElements.get( 0 ) instanceof JsonPathHelper.JsonAttribute;
|
||||
final boolean needsWrapping = jsonPathElements.get( 0 ) instanceof JsonPathHelper.JsonAttribute
|
||||
&& jsonDocument.getColumnReference() != null
|
||||
|| !isJson;
|
||||
if ( needsWrapping ) {
|
||||
sqlAppender.appendSql( '(' );
|
||||
}
|
||||
jsonDocument.accept( walker );
|
||||
if ( needsWrapping ) {
|
||||
if ( !isJson ) {
|
||||
sqlAppender.append( " format json" );
|
||||
}
|
||||
sqlAppender.appendSql( ')' );
|
||||
}
|
||||
for ( int i = 0; i < jsonPathElements.size(); i++ ) {
|
||||
final JsonPathHelper.JsonPathElement jsonPathElement = jsonPathElements.get( i );
|
||||
if ( jsonPathElement instanceof JsonPathHelper.JsonAttribute attribute ) {
|
||||
final String attributeName = attribute.attribute();
|
||||
appendInDoubleQuotes( sqlAppender, attributeName );
|
||||
sqlAppender.appendSql( "." );
|
||||
sqlAppender.appendDoubleQuoteEscapedString( attributeName );
|
||||
}
|
||||
else if ( jsonPathElement instanceof JsonPathHelper.JsonParameterIndexAccess ) {
|
||||
assert passingClause != null;
|
||||
final String parameterName = ( (JsonPathHelper.JsonParameterIndexAccess) jsonPathElement ).parameterName();
|
||||
final Expression expression = passingClause.getPassingExpressions().get( parameterName );
|
||||
if ( expression == null ) {
|
||||
throw new QueryException( "JSON path [" + jsonPath + "] uses parameter [" + parameterName + "] that is not passed" );
|
||||
}
|
||||
|
||||
sqlAppender.appendSql( '[' );
|
||||
expression.accept( walker );
|
||||
sqlAppender.appendSql( "+1]" );
|
||||
}
|
||||
else {
|
||||
sqlAppender.appendSql( '[' );
|
||||
|
@ -101,18 +133,5 @@ public class H2JsonValueFunction extends JsonValueFunction {
|
|||
sqlAppender.appendSql( ']' );
|
||||
}
|
||||
}
|
||||
sqlAppender.appendSql( " as varchar)" );
|
||||
}
|
||||
|
||||
private static void appendInDoubleQuotes(SqlAppender sqlAppender, String attributeName) {
|
||||
sqlAppender.appendSql( ".\"" );
|
||||
for ( int j = 0; j < attributeName.length(); j++ ) {
|
||||
final char c = attributeName.charAt( j );
|
||||
if ( c == '"' ) {
|
||||
sqlAppender.appendSql( '"' );
|
||||
}
|
||||
sqlAppender.appendSql( c );
|
||||
}
|
||||
sqlAppender.appendSql( '"' );
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
|
||||
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
|
||||
*/
|
||||
package org.hibernate.dialect.function.json;
|
||||
|
||||
import org.hibernate.query.ReturnableType;
|
||||
import org.hibernate.sql.ast.SqlAstTranslator;
|
||||
import org.hibernate.sql.ast.spi.SqlAppender;
|
||||
import org.hibernate.sql.ast.tree.expression.Expression;
|
||||
import org.hibernate.sql.ast.tree.expression.JsonExistsErrorBehavior;
|
||||
import org.hibernate.sql.ast.tree.expression.JsonPathPassingClause;
|
||||
import org.hibernate.type.spi.TypeConfiguration;
|
||||
|
||||
/**
|
||||
* SAP HANA json_exists function.
|
||||
*/
|
||||
public class HANAJsonExistsFunction extends JsonExistsFunction {
|
||||
|
||||
public HANAJsonExistsFunction(TypeConfiguration typeConfiguration) {
|
||||
super( typeConfiguration, true, false );
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void render(
|
||||
SqlAppender sqlAppender,
|
||||
JsonExistsArguments arguments,
|
||||
ReturnableType<?> returnType,
|
||||
SqlAstTranslator<?> walker) {
|
||||
sqlAppender.appendSql( "json_query(" );
|
||||
arguments.jsonDocument().accept( walker );
|
||||
sqlAppender.appendSql( ',' );
|
||||
final Expression jsonPath = arguments.jsonPath();
|
||||
final JsonPathPassingClause passingClause = arguments.passingClause();
|
||||
if ( passingClause == null ) {
|
||||
jsonPath.accept( walker );
|
||||
}
|
||||
else {
|
||||
JsonPathHelper.appendInlinedJsonPathIncludingPassingClause(
|
||||
sqlAppender,
|
||||
"",
|
||||
arguments.jsonPath(),
|
||||
passingClause,
|
||||
walker
|
||||
);
|
||||
}
|
||||
|
||||
final JsonExistsErrorBehavior errorBehavior = arguments.errorBehavior();
|
||||
if ( errorBehavior != null && errorBehavior != JsonExistsErrorBehavior.FALSE ) {
|
||||
if ( errorBehavior == JsonExistsErrorBehavior.TRUE ) {
|
||||
sqlAppender.appendSql( " empty object on error" );
|
||||
}
|
||||
else {
|
||||
sqlAppender.appendSql( " error on error" );
|
||||
}
|
||||
}
|
||||
sqlAppender.appendSql( ") is not null" );
|
||||
}
|
||||
}
|
|
@ -106,15 +106,7 @@ public class HANAJsonObjectFunction extends JsonObjectFunction {
|
|||
value.accept( walker );
|
||||
sqlAppender.appendSql( ' ' );
|
||||
final String literalValue = walker.getLiteralValue( (Expression) key );
|
||||
sqlAppender.appendSql( '"' );
|
||||
for ( int j = 0; j < literalValue.length(); j++ ) {
|
||||
final char c = literalValue.charAt( j );
|
||||
if ( c == '"' ) {
|
||||
sqlAppender.appendSql( '"' );
|
||||
}
|
||||
sqlAppender.appendSql( c );
|
||||
}
|
||||
sqlAppender.appendSql( '"' );
|
||||
sqlAppender.appendDoubleQuoteEscapedString( literalValue );
|
||||
separator = ',';
|
||||
}
|
||||
sqlAppender.appendSql( " from sys.dummy for json('arraywrap'='no'" );
|
||||
|
|
|
@ -0,0 +1,190 @@
|
|||
/*
|
||||
* 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.SqmJsonExistsExpression;
|
||||
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.JsonExistsErrorBehavior;
|
||||
import org.hibernate.sql.ast.tree.expression.JsonPathPassingClause;
|
||||
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_exists function.
|
||||
*/
|
||||
public class JsonExistsFunction extends AbstractSqmSelfRenderingFunctionDescriptor {
|
||||
|
||||
protected final boolean supportsJsonPathExpression;
|
||||
protected final boolean supportsJsonPathPassingClause;
|
||||
|
||||
public JsonExistsFunction(
|
||||
TypeConfiguration typeConfiguration,
|
||||
boolean supportsJsonPathExpression,
|
||||
boolean supportsJsonPathPassingClause) {
|
||||
super(
|
||||
"json_exists",
|
||||
FunctionKind.NORMAL,
|
||||
StandardArgumentsValidators.composite(
|
||||
new ArgumentTypesValidator( StandardArgumentsValidators.between( 2, 3 ), IMPLICIT_JSON, STRING, ANY )
|
||||
),
|
||||
StandardFunctionReturnTypeResolvers.invariant( typeConfiguration.standardBasicTypeForJavaType( Boolean.class ) ),
|
||||
StandardFunctionArgumentTypeResolvers.invariant( typeConfiguration, JSON, STRING )
|
||||
);
|
||||
this.supportsJsonPathExpression = supportsJsonPathExpression;
|
||||
this.supportsJsonPathPassingClause = supportsJsonPathPassingClause;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPredicate() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected <T> SelfRenderingSqmFunction<T> generateSqmFunctionExpression(
|
||||
List<? extends SqmTypedNode<?>> arguments,
|
||||
ReturnableType<T> impliedResultType,
|
||||
QueryEngine queryEngine) {
|
||||
//noinspection unchecked
|
||||
return (SelfRenderingSqmFunction<T>) new SqmJsonExistsExpression(
|
||||
this,
|
||||
this,
|
||||
arguments,
|
||||
(ReturnableType<Boolean>) impliedResultType,
|
||||
getArgumentsValidator(),
|
||||
getReturnTypeResolver(),
|
||||
queryEngine.getCriteriaBuilder(),
|
||||
getName()
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(
|
||||
SqlAppender sqlAppender,
|
||||
List<? extends SqlAstNode> sqlAstArguments,
|
||||
ReturnableType<?> returnType,
|
||||
SqlAstTranslator<?> walker) {
|
||||
render( sqlAppender, JsonExistsArguments.extract( sqlAstArguments ), returnType, walker );
|
||||
}
|
||||
|
||||
protected void render(
|
||||
SqlAppender sqlAppender,
|
||||
JsonExistsArguments arguments,
|
||||
ReturnableType<?> returnType,
|
||||
SqlAstTranslator<?> walker) {
|
||||
sqlAppender.appendSql( "json_exists(" );
|
||||
arguments.jsonDocument().accept( walker );
|
||||
sqlAppender.appendSql( ',' );
|
||||
final Expression jsonPath = arguments.jsonPath();
|
||||
final JsonPathPassingClause passingClause = arguments.passingClause();
|
||||
if ( supportsJsonPathPassingClause || passingClause == null ) {
|
||||
if ( supportsJsonPathExpression ) {
|
||||
jsonPath.accept( walker );
|
||||
}
|
||||
else {
|
||||
walker.getSessionFactory().getJdbcServices().getDialect().appendLiteral(
|
||||
sqlAppender,
|
||||
walker.getLiteralValue( 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
|
||||
);
|
||||
}
|
||||
final JsonExistsErrorBehavior errorBehavior = arguments.errorBehavior();
|
||||
if ( errorBehavior != null && errorBehavior != JsonExistsErrorBehavior.FALSE ) {
|
||||
if ( errorBehavior == JsonExistsErrorBehavior.TRUE ) {
|
||||
sqlAppender.appendSql( " true on error" );
|
||||
}
|
||||
else {
|
||||
sqlAppender.appendSql( " error on error" );
|
||||
}
|
||||
}
|
||||
sqlAppender.appendSql( ')' );
|
||||
}
|
||||
|
||||
protected record JsonExistsArguments(
|
||||
Expression jsonDocument,
|
||||
Expression jsonPath,
|
||||
boolean isJsonType,
|
||||
@Nullable JsonPathPassingClause passingClause,
|
||||
@Nullable JsonExistsErrorBehavior errorBehavior) {
|
||||
public static JsonExistsArguments extract(List<? extends SqlAstNode> sqlAstArguments) {
|
||||
int nextIndex = 2;
|
||||
JsonPathPassingClause passingClause = null;
|
||||
JsonExistsErrorBehavior errorBehavior = 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 JsonExistsErrorBehavior ) {
|
||||
errorBehavior = (JsonExistsErrorBehavior) node;
|
||||
nextIndex++;
|
||||
}
|
||||
}
|
||||
final Expression jsonDocument = (Expression) sqlAstArguments.get( 0 );
|
||||
return new JsonExistsArguments(
|
||||
jsonDocument,
|
||||
(Expression) sqlAstArguments.get( 1 ),
|
||||
jsonDocument.getExpressionType() != null
|
||||
&& jsonDocument.getExpressionType().getSingleJdbcMapping().getJdbcType().isJson(),
|
||||
passingClause,
|
||||
errorBehavior
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -10,6 +10,10 @@ import java.util.ArrayList;
|
|||
import java.util.List;
|
||||
|
||||
import org.hibernate.QueryException;
|
||||
import org.hibernate.sql.ast.SqlAstTranslator;
|
||||
import org.hibernate.sql.ast.spi.SqlAppender;
|
||||
import org.hibernate.sql.ast.tree.expression.Expression;
|
||||
import org.hibernate.sql.ast.tree.expression.JsonPathPassingClause;
|
||||
|
||||
public class JsonPathHelper {
|
||||
|
||||
|
@ -47,6 +51,112 @@ public class JsonPathHelper {
|
|||
return jsonPathElements;
|
||||
}
|
||||
|
||||
public static void appendJsonPathConcatPassingClause(
|
||||
SqlAppender sqlAppender,
|
||||
Expression jsonPathExpression,
|
||||
JsonPathPassingClause passingClause, SqlAstTranslator<?> walker) {
|
||||
appendJsonPathConcatenatedPassingClause( sqlAppender, jsonPathExpression, passingClause, walker, "concat", "," );
|
||||
}
|
||||
|
||||
public static void appendJsonPathDoublePipePassingClause(
|
||||
SqlAppender sqlAppender,
|
||||
Expression jsonPathExpression,
|
||||
JsonPathPassingClause passingClause,
|
||||
SqlAstTranslator<?> walker) {
|
||||
appendJsonPathConcatenatedPassingClause( sqlAppender, jsonPathExpression, passingClause, walker, "", "||" );
|
||||
}
|
||||
|
||||
public static void appendInlinedJsonPathIncludingPassingClause(
|
||||
SqlAppender sqlAppender,
|
||||
String prefix,
|
||||
Expression jsonPathExpression,
|
||||
JsonPathPassingClause passingClause,
|
||||
SqlAstTranslator<?> walker) {
|
||||
final String jsonPath = walker.getLiteralValue( jsonPathExpression );
|
||||
final String[] parts = jsonPath.split( "\\$" );
|
||||
sqlAppender.append( '\'' );
|
||||
sqlAppender.append( prefix );
|
||||
final int start;
|
||||
if ( parts[0].isEmpty() ) {
|
||||
start = 2;
|
||||
sqlAppender.append( '$' );
|
||||
sqlAppender.append( parts[1] );
|
||||
}
|
||||
else {
|
||||
start = 0;
|
||||
}
|
||||
for ( int i = start; i < parts.length; i++ ) {
|
||||
final String part = parts[i];
|
||||
|
||||
final int parameterNameEndIndex = indexOfNonIdentifier( part, 0 );
|
||||
final String parameterName = part.substring( 0, parameterNameEndIndex );
|
||||
final Expression expression = passingClause.getPassingExpressions().get( parameterName );
|
||||
if ( expression == null ) {
|
||||
throw new QueryException( "JSON path [" + jsonPath + "] uses parameter [" + parameterName + "] that is not passed" );
|
||||
}
|
||||
Object literalValue = walker.getLiteralValue( expression );
|
||||
if ( literalValue instanceof String ) {
|
||||
appendLiteral( sqlAppender, 0, (String) literalValue );
|
||||
}
|
||||
else {
|
||||
sqlAppender.appendSql( String.valueOf( literalValue ) );
|
||||
}
|
||||
appendLiteral( sqlAppender, parameterNameEndIndex, part );
|
||||
}
|
||||
sqlAppender.appendSql( '\'' );
|
||||
}
|
||||
|
||||
private static void appendLiteral(SqlAppender sqlAppender, int parameterNameEndIndex, String part) {
|
||||
for ( int j = parameterNameEndIndex; j < part.length(); j++ ) {
|
||||
final char c = part.charAt( j );
|
||||
if ( c == '\'') {
|
||||
sqlAppender.appendSql( '\'' );
|
||||
}
|
||||
sqlAppender.appendSql( c );
|
||||
}
|
||||
}
|
||||
|
||||
private static void appendJsonPathConcatenatedPassingClause(
|
||||
SqlAppender sqlAppender,
|
||||
Expression jsonPathExpression,
|
||||
JsonPathPassingClause passingClause,
|
||||
SqlAstTranslator<?> walker,
|
||||
String concatStart,
|
||||
String concatCombine) {
|
||||
final String jsonPath = walker.getLiteralValue( jsonPathExpression );
|
||||
final String[] parts = jsonPath.split( "\\$" );
|
||||
sqlAppender.append( concatStart );
|
||||
final int start;
|
||||
String separator = "(";
|
||||
if ( parts[0].isEmpty() ) {
|
||||
start = 2;
|
||||
sqlAppender.append( separator );
|
||||
sqlAppender.append( "'$'" );
|
||||
sqlAppender.append( concatCombine );
|
||||
sqlAppender.appendSingleQuoteEscapedString( parts[1] );
|
||||
separator = concatCombine;
|
||||
}
|
||||
else {
|
||||
start = 0;
|
||||
}
|
||||
for ( int i = start; i < parts.length; i++ ) {
|
||||
final String part = parts[i];
|
||||
sqlAppender.append( separator );
|
||||
|
||||
final int parameterNameEndIndex = indexOfNonIdentifier( part, 0 );
|
||||
final String parameterName = part.substring( 0, parameterNameEndIndex );
|
||||
final Expression expression = passingClause.getPassingExpressions().get( parameterName );
|
||||
if ( expression == null ) {
|
||||
throw new QueryException( "JSON path [" + jsonPath + "] uses parameter [" + parameterName + "] that is not passed" );
|
||||
}
|
||||
expression.accept( walker );
|
||||
sqlAppender.append( ',' );
|
||||
sqlAppender.appendSingleQuoteEscapedString( part.substring( parameterNameEndIndex ) );
|
||||
separator = concatCombine;
|
||||
}
|
||||
sqlAppender.appendSql( ')' );
|
||||
}
|
||||
|
||||
private static void parseAttribute(String jsonPath, int startIndex, int endIndex, ArrayList<JsonPathElement> jsonPathElements) {
|
||||
final int bracketIndex = jsonPath.indexOf( '[', startIndex );
|
||||
if ( bracketIndex != -1 && bracketIndex < endIndex ) {
|
||||
|
@ -64,11 +174,40 @@ public class JsonPathHelper {
|
|||
if ( bracketEndIndex < bracketStartIndex ) {
|
||||
throw new QueryException( "Can't emulate non-simple json path expression: " + jsonPath );
|
||||
}
|
||||
final int index = Integer.parseInt( jsonPath, bracketStartIndex + 1, bracketEndIndex, 10 );
|
||||
jsonPathElements.add( new JsonIndexAccess( index ) );
|
||||
final int contentStartIndex = indexOfNonWhitespace( jsonPath, bracketStartIndex + 1 );
|
||||
final int contentEndIndex = lastIndexOfWhitespace( jsonPath, bracketEndIndex - 1 );
|
||||
if ( jsonPath.charAt( contentStartIndex ) == '$' ) {
|
||||
jsonPathElements.add( new JsonParameterIndexAccess( jsonPath.substring( contentStartIndex + 1, contentEndIndex ) ) );
|
||||
}
|
||||
else {
|
||||
final int index = Integer.parseInt( jsonPath, contentStartIndex, contentEndIndex, 10 );
|
||||
jsonPathElements.add( new JsonIndexAccess( index ) );
|
||||
}
|
||||
}
|
||||
|
||||
public static int indexOfNonIdentifier(String jsonPath, int i) {
|
||||
while ( i < jsonPath.length() && Character.isJavaIdentifierPart( jsonPath.charAt( i ) ) ) {
|
||||
i++;
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
private static int indexOfNonWhitespace(String jsonPath, int i) {
|
||||
while ( i < jsonPath.length() && Character.isWhitespace( jsonPath.charAt( i ) ) ) {
|
||||
i++;
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
private static int lastIndexOfWhitespace(String jsonPath, int i) {
|
||||
while ( i > 0 && Character.isWhitespace( jsonPath.charAt( i ) ) ) {
|
||||
i--;
|
||||
}
|
||||
return i + 1;
|
||||
}
|
||||
|
||||
public sealed interface JsonPathElement {}
|
||||
public record JsonAttribute(String attribute) implements JsonPathElement {}
|
||||
public record JsonIndexAccess(int index) implements JsonPathElement {}
|
||||
public record JsonParameterIndexAccess(String parameterName) implements JsonPathElement {}
|
||||
}
|
||||
|
|
|
@ -6,7 +6,9 @@
|
|||
*/
|
||||
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;
|
||||
|
@ -23,6 +25,7 @@ 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.JsonValueEmptyBehavior;
|
||||
import org.hibernate.sql.ast.tree.expression.JsonValueErrorBehavior;
|
||||
import org.hibernate.type.spi.TypeConfiguration;
|
||||
|
@ -40,8 +43,12 @@ import static org.hibernate.query.sqm.produce.function.FunctionParameterType.STR
|
|||
public class JsonValueFunction extends AbstractSqmSelfRenderingFunctionDescriptor {
|
||||
|
||||
protected final boolean supportsJsonPathExpression;
|
||||
protected final boolean supportsJsonPathPassingClause;
|
||||
|
||||
public JsonValueFunction(TypeConfiguration typeConfiguration, boolean supportsJsonPathExpression) {
|
||||
public JsonValueFunction(
|
||||
TypeConfiguration typeConfiguration,
|
||||
boolean supportsJsonPathExpression,
|
||||
boolean supportsJsonPathPassingClause) {
|
||||
super(
|
||||
"json_value",
|
||||
FunctionKind.NORMAL,
|
||||
|
@ -52,6 +59,7 @@ public class JsonValueFunction extends AbstractSqmSelfRenderingFunctionDescripto
|
|||
StandardFunctionArgumentTypeResolvers.invariant( typeConfiguration, JSON, STRING )
|
||||
);
|
||||
this.supportsJsonPathExpression = supportsJsonPathExpression;
|
||||
this.supportsJsonPathPassingClause = supportsJsonPathPassingClause;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -88,16 +96,43 @@ public class JsonValueFunction extends AbstractSqmSelfRenderingFunctionDescripto
|
|||
sqlAppender.appendSql( "json_value(" );
|
||||
arguments.jsonDocument().accept( walker );
|
||||
sqlAppender.appendSql( ',' );
|
||||
if ( supportsJsonPathExpression ) {
|
||||
arguments.jsonPath().accept( walker );
|
||||
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 {
|
||||
walker.getSessionFactory().getJdbcServices().getDialect().appendLiteral(
|
||||
JsonPathHelper.appendInlinedJsonPathIncludingPassingClause(
|
||||
sqlAppender,
|
||||
walker.getLiteralValue( arguments.jsonPath() )
|
||||
"",
|
||||
arguments.jsonPath(),
|
||||
passingClause,
|
||||
walker
|
||||
);
|
||||
}
|
||||
|
||||
if ( arguments.returningType() != null ) {
|
||||
sqlAppender.appendSql( " returning " );
|
||||
arguments.returningType().accept( walker );
|
||||
|
@ -133,11 +168,13 @@ public class JsonValueFunction extends AbstractSqmSelfRenderingFunctionDescripto
|
|||
Expression jsonDocument,
|
||||
Expression jsonPath,
|
||||
boolean isJsonType,
|
||||
@Nullable JsonPathPassingClause passingClause,
|
||||
@Nullable CastTarget returningType,
|
||||
@Nullable JsonValueErrorBehavior errorBehavior,
|
||||
@Nullable JsonValueEmptyBehavior emptyBehavior) {
|
||||
public static JsonValueArguments extract(List<? extends SqlAstNode> sqlAstArguments) {
|
||||
int nextIndex = 2;
|
||||
JsonPathPassingClause passingClause = null;
|
||||
CastTarget castTarget = null;
|
||||
JsonValueErrorBehavior errorBehavior = null;
|
||||
JsonValueEmptyBehavior emptyBehavior = null;
|
||||
|
@ -148,6 +185,13 @@ public class JsonValueFunction extends AbstractSqmSelfRenderingFunctionDescripto
|
|||
nextIndex++;
|
||||
}
|
||||
}
|
||||
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 JsonValueErrorBehavior ) {
|
||||
|
@ -167,6 +211,7 @@ public class JsonValueFunction extends AbstractSqmSelfRenderingFunctionDescripto
|
|||
(Expression) sqlAstArguments.get( 1 ),
|
||||
jsonDocument.getExpressionType() != null
|
||||
&& jsonDocument.getExpressionType().getSingleJdbcMapping().getJdbcType().isJson(),
|
||||
passingClause,
|
||||
castTarget,
|
||||
errorBehavior,
|
||||
emptyBehavior
|
||||
|
|
|
@ -10,6 +10,7 @@ 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.JsonValueEmptyBehavior;
|
||||
import org.hibernate.sql.ast.tree.expression.JsonValueErrorBehavior;
|
||||
import org.hibernate.type.spi.TypeConfiguration;
|
||||
|
@ -20,7 +21,7 @@ import org.hibernate.type.spi.TypeConfiguration;
|
|||
public class MariaDBJsonValueFunction extends JsonValueFunction {
|
||||
|
||||
public MariaDBJsonValueFunction(TypeConfiguration typeConfiguration) {
|
||||
super( typeConfiguration, true );
|
||||
super( typeConfiguration, true, false );
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -42,7 +43,17 @@ public class MariaDBJsonValueFunction extends JsonValueFunction {
|
|||
sqlAppender.appendSql( "json_unquote(nullif(json_extract(" );
|
||||
arguments.jsonDocument().accept( walker );
|
||||
sqlAppender.appendSql( "," );
|
||||
arguments.jsonPath().accept( walker );
|
||||
final JsonPathPassingClause passingClause = arguments.passingClause();
|
||||
if ( passingClause == null ) {
|
||||
arguments.jsonPath().accept( walker );
|
||||
}
|
||||
else {
|
||||
JsonPathHelper.appendJsonPathConcatPassingClause(
|
||||
sqlAppender,
|
||||
arguments.jsonPath(),
|
||||
passingClause, walker
|
||||
);
|
||||
}
|
||||
sqlAppender.appendSql( "),'null'))" );
|
||||
if ( arguments.returningType() != null ) {
|
||||
sqlAppender.appendSql( " as " );
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* 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.type.spi.TypeConfiguration;
|
||||
|
||||
/**
|
||||
* MySQL json_exists function.
|
||||
*/
|
||||
public class MySQLJsonExistsFunction extends JsonExistsFunction {
|
||||
|
||||
public MySQLJsonExistsFunction(TypeConfiguration typeConfiguration) {
|
||||
super( typeConfiguration, true, false );
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void render(
|
||||
SqlAppender sqlAppender,
|
||||
JsonExistsArguments arguments,
|
||||
ReturnableType<?> returnType,
|
||||
SqlAstTranslator<?> walker) {
|
||||
final JsonPathPassingClause passingClause = arguments.passingClause();
|
||||
sqlAppender.appendSql( "json_contains_path(" );
|
||||
arguments.jsonDocument().accept( walker );
|
||||
sqlAppender.appendSql( ",'one'," );
|
||||
if ( passingClause == null ) {
|
||||
arguments.jsonPath().accept( walker );
|
||||
}
|
||||
else {
|
||||
JsonPathHelper.appendJsonPathConcatPassingClause(
|
||||
sqlAppender,
|
||||
arguments.jsonPath(),
|
||||
passingClause, walker
|
||||
);
|
||||
}
|
||||
sqlAppender.appendSql( ')' );
|
||||
}
|
||||
}
|
|
@ -9,6 +9,7 @@ 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.JsonValueEmptyBehavior;
|
||||
import org.hibernate.sql.ast.tree.expression.JsonValueErrorBehavior;
|
||||
import org.hibernate.type.spi.TypeConfiguration;
|
||||
|
@ -19,7 +20,7 @@ import org.hibernate.type.spi.TypeConfiguration;
|
|||
public class MySQLJsonValueFunction extends JsonValueFunction {
|
||||
|
||||
public MySQLJsonValueFunction(TypeConfiguration typeConfiguration) {
|
||||
super( typeConfiguration, true );
|
||||
super( typeConfiguration, true, false );
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -42,7 +43,17 @@ public class MySQLJsonValueFunction extends JsonValueFunction {
|
|||
sqlAppender.appendSql( "json_unquote(nullif(json_extract(" );
|
||||
arguments.jsonDocument().accept( walker );
|
||||
sqlAppender.appendSql( "," );
|
||||
arguments.jsonPath().accept( walker );
|
||||
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)))" );
|
||||
if ( arguments.returningType() != null ) {
|
||||
sqlAppender.appendSql( " as " );
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* 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.query.ReturnableType;
|
||||
import org.hibernate.sql.ast.SqlAstTranslator;
|
||||
import org.hibernate.sql.ast.spi.SqlAppender;
|
||||
import org.hibernate.sql.ast.tree.expression.Expression;
|
||||
import org.hibernate.sql.ast.tree.expression.JsonPathPassingClause;
|
||||
import org.hibernate.type.spi.TypeConfiguration;
|
||||
|
||||
/**
|
||||
* PostgreSQL json_exists function.
|
||||
*/
|
||||
public class PostgreSQLJsonExistsFunction extends JsonExistsFunction {
|
||||
|
||||
public PostgreSQLJsonExistsFunction(TypeConfiguration typeConfiguration) {
|
||||
super( typeConfiguration, true, true );
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void render(
|
||||
SqlAppender sqlAppender,
|
||||
JsonExistsArguments arguments,
|
||||
ReturnableType<?> returnType,
|
||||
SqlAstTranslator<?> walker) {
|
||||
sqlAppender.appendSql( "jsonb_path_exists(" );
|
||||
arguments.jsonDocument().accept( walker );
|
||||
sqlAppender.appendSql( ',' );
|
||||
arguments.jsonPath().accept( walker );
|
||||
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( ')' );
|
||||
}
|
||||
sqlAppender.appendSql( ')' );
|
||||
}
|
||||
}
|
|
@ -6,12 +6,16 @@
|
|||
*/
|
||||
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.JsonValueEmptyBehavior;
|
||||
import org.hibernate.sql.ast.tree.expression.JsonValueErrorBehavior;
|
||||
import org.hibernate.sql.ast.tree.expression.Literal;
|
||||
|
@ -23,7 +27,7 @@ import org.hibernate.type.spi.TypeConfiguration;
|
|||
public class PostgreSQLJsonValueFunction extends JsonValueFunction {
|
||||
|
||||
public PostgreSQLJsonValueFunction(TypeConfiguration typeConfiguration) {
|
||||
super( typeConfiguration, true );
|
||||
super( typeConfiguration, true, true );
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -61,6 +65,19 @@ public class PostgreSQLJsonValueFunction extends JsonValueFunction {
|
|||
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( ")#>>'{}'" );
|
||||
if ( arguments.returningType() != null ) {
|
||||
|
|
|
@ -0,0 +1,88 @@
|
|||
/*
|
||||
* 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.JsonExistsErrorBehavior;
|
||||
import org.hibernate.sql.ast.tree.expression.JsonPathPassingClause;
|
||||
import org.hibernate.sql.ast.tree.expression.JsonValueEmptyBehavior;
|
||||
import org.hibernate.type.spi.TypeConfiguration;
|
||||
|
||||
/**
|
||||
* SQL Server json_exists function.
|
||||
*/
|
||||
public class SQLServerJsonExistsFunction extends JsonExistsFunction {
|
||||
|
||||
public SQLServerJsonExistsFunction(TypeConfiguration typeConfiguration) {
|
||||
super( typeConfiguration, true, false );
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPredicate() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void render(
|
||||
SqlAppender sqlAppender,
|
||||
JsonExistsArguments arguments,
|
||||
ReturnableType<?> returnType,
|
||||
SqlAstTranslator<?> walker) {
|
||||
if ( arguments.errorBehavior() == JsonExistsErrorBehavior.TRUE ) {
|
||||
throw new QueryException( "Can't emulate json_exists(... true on error) on SQL Server" );
|
||||
}
|
||||
if ( arguments.errorBehavior() == JsonExistsErrorBehavior.ERROR ) {
|
||||
sqlAppender.append( '(' );
|
||||
}
|
||||
sqlAppender.appendSql( "json_path_exists(" );
|
||||
arguments.jsonDocument().accept( walker );
|
||||
sqlAppender.appendSql( ',' );
|
||||
final JsonPathPassingClause passingClause = arguments.passingClause();
|
||||
if ( passingClause != null ) {
|
||||
JsonPathHelper.appendInlinedJsonPathIncludingPassingClause(
|
||||
sqlAppender,
|
||||
"",
|
||||
arguments.jsonPath(),
|
||||
passingClause,
|
||||
walker
|
||||
);
|
||||
}
|
||||
else {
|
||||
walker.getSessionFactory().getJdbcServices().getDialect().appendLiteral(
|
||||
sqlAppender,
|
||||
walker.getLiteralValue( arguments.jsonPath() )
|
||||
);
|
||||
}
|
||||
sqlAppender.appendSql( ')' );
|
||||
if ( arguments.errorBehavior() == JsonExistsErrorBehavior.ERROR ) {
|
||||
// json_path_exists returns 0 if an invalid JSON is given,
|
||||
// so we have to run openjson to be sure the json is valid and potentially throw an error
|
||||
sqlAppender.appendSql( "=1 or (select v from openjson(" );
|
||||
arguments.jsonDocument().accept( walker );
|
||||
sqlAppender.appendSql( ") with (v varchar(max) " );
|
||||
if ( passingClause != null ) {
|
||||
JsonPathHelper.appendInlinedJsonPathIncludingPassingClause(
|
||||
sqlAppender,
|
||||
"",
|
||||
arguments.jsonPath(),
|
||||
passingClause,
|
||||
walker
|
||||
);
|
||||
}
|
||||
else {
|
||||
walker.getSessionFactory().getJdbcServices().getDialect().appendLiteral(
|
||||
sqlAppender,
|
||||
walker.getLiteralValue( arguments.jsonPath() )
|
||||
);
|
||||
}
|
||||
sqlAppender.appendSql( ")) is null)" );
|
||||
}
|
||||
}
|
||||
}
|
|
@ -10,7 +10,7 @@ import org.hibernate.QueryException;
|
|||
import org.hibernate.query.ReturnableType;
|
||||
import org.hibernate.sql.ast.SqlAstTranslator;
|
||||
import org.hibernate.sql.ast.spi.SqlAppender;
|
||||
import org.hibernate.sql.ast.tree.expression.Expression;
|
||||
import org.hibernate.sql.ast.tree.expression.JsonPathPassingClause;
|
||||
import org.hibernate.sql.ast.tree.expression.JsonValueEmptyBehavior;
|
||||
import org.hibernate.sql.ast.tree.expression.JsonValueErrorBehavior;
|
||||
import org.hibernate.type.spi.TypeConfiguration;
|
||||
|
@ -21,7 +21,7 @@ import org.hibernate.type.spi.TypeConfiguration;
|
|||
public class SQLServerJsonValueFunction extends JsonValueFunction {
|
||||
|
||||
public SQLServerJsonValueFunction(TypeConfiguration typeConfiguration) {
|
||||
super( typeConfiguration, true );
|
||||
super( typeConfiguration, true, false );
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -36,7 +36,7 @@ public class SQLServerJsonValueFunction extends JsonValueFunction {
|
|||
}
|
||||
sqlAppender.appendSql( "(select v from openjson(" );
|
||||
arguments.jsonDocument().accept( walker );
|
||||
sqlAppender.appendSql( ",'$') with (v " );
|
||||
sqlAppender.appendSql( ") with (v " );
|
||||
if ( arguments.returningType() != null ) {
|
||||
arguments.returningType().accept( walker );
|
||||
}
|
||||
|
@ -44,10 +44,32 @@ public class SQLServerJsonValueFunction extends JsonValueFunction {
|
|||
sqlAppender.appendSql( "varchar(max)" );
|
||||
}
|
||||
sqlAppender.appendSql( ' ' );
|
||||
final JsonPathPassingClause passingClause = arguments.passingClause();
|
||||
if ( arguments.emptyBehavior() != null && arguments.emptyBehavior() != JsonValueEmptyBehavior.NULL ) {
|
||||
walker.getSessionFactory().getJdbcServices().getDialect().appendLiteral(
|
||||
// The strict modifier will cause an error to be thrown if a field doesn't exist
|
||||
if ( passingClause != null ) {
|
||||
JsonPathHelper.appendInlinedJsonPathIncludingPassingClause(
|
||||
sqlAppender,
|
||||
"strict ",
|
||||
arguments.jsonPath(),
|
||||
passingClause,
|
||||
walker
|
||||
);
|
||||
}
|
||||
else {
|
||||
walker.getSessionFactory().getJdbcServices().getDialect().appendLiteral(
|
||||
sqlAppender,
|
||||
"strict " + walker.getLiteralValue( arguments.jsonPath() )
|
||||
);
|
||||
}
|
||||
}
|
||||
else if ( passingClause != null ) {
|
||||
JsonPathHelper.appendInlinedJsonPathIncludingPassingClause(
|
||||
sqlAppender,
|
||||
"strict " + walker.getLiteralValue( arguments.jsonPath() )
|
||||
"",
|
||||
arguments.jsonPath(),
|
||||
passingClause,
|
||||
walker
|
||||
);
|
||||
}
|
||||
else {
|
||||
|
|
|
@ -4,6 +4,8 @@
|
|||
*/
|
||||
package org.hibernate.internal.util;
|
||||
|
||||
import org.hibernate.sql.ast.spi.SqlAppender;
|
||||
|
||||
public final class QuotingHelper {
|
||||
|
||||
private QuotingHelper() { /* static methods only - hide constructor */
|
||||
|
@ -150,4 +152,25 @@ public final class QuotingHelper {
|
|||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public static void appendDoubleQuoteEscapedString(StringBuilder sb, String text) {
|
||||
appendWithDoubleEscaping( sb, text, '"' );
|
||||
}
|
||||
|
||||
public static void appendSingleQuoteEscapedString(StringBuilder sb, String text) {
|
||||
appendWithDoubleEscaping( sb, text, '\'' );
|
||||
}
|
||||
|
||||
private static void appendWithDoubleEscaping(StringBuilder sb, String text, char quoteChar) {
|
||||
sb.append( quoteChar );
|
||||
for ( int i = 0; i < text.length(); i++ ) {
|
||||
final char c = text.charAt( i );
|
||||
if ( c == quoteChar ) {
|
||||
sb.append( quoteChar );
|
||||
}
|
||||
sb.append( c );
|
||||
}
|
||||
sb.append( quoteChar );
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -3691,7 +3691,7 @@ public interface HibernateCriteriaBuilder extends CriteriaBuilder {
|
|||
JpaJsonValueExpression<String> jsonValue(Expression<?> jsonDocument, String jsonPath);
|
||||
|
||||
/**
|
||||
* Extracts a value by JSON path from a json document.
|
||||
* Extracts a value by JSON path from a JSON document.
|
||||
*
|
||||
* @since 7.0
|
||||
*/
|
||||
|
@ -3706,13 +3706,29 @@ public interface HibernateCriteriaBuilder extends CriteriaBuilder {
|
|||
JpaJsonValueExpression<String> jsonValue(Expression<?> jsonDocument, Expression<String> jsonPath);
|
||||
|
||||
/**
|
||||
* Extracts a value by JSON path from a json document.
|
||||
* Extracts a value by JSON path from a JSON document.
|
||||
*
|
||||
* @since 7.0
|
||||
*/
|
||||
@Incubating
|
||||
<T> JpaJsonValueExpression<T> jsonValue(Expression<?> jsonDocument, Expression<String> jsonPath, Class<T> returningType);
|
||||
|
||||
/**
|
||||
* Checks if a JSON document contains a node for the given JSON path.
|
||||
*
|
||||
* @since 7.0
|
||||
*/
|
||||
@Incubating
|
||||
JpaJsonExistsExpression jsonExists(Expression<?> jsonDocument, String jsonPath);
|
||||
|
||||
/**
|
||||
* Checks if a JSON document contains a node for the given JSON path.
|
||||
*
|
||||
* @since 7.0
|
||||
*/
|
||||
@Incubating
|
||||
JpaJsonExistsExpression jsonExists(Expression<?> jsonDocument, Expression<String> jsonPath);
|
||||
|
||||
/**
|
||||
* Create a JSON object from the given map of key values.
|
||||
*
|
||||
|
|
|
@ -0,0 +1,79 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
|
||||
*/
|
||||
package org.hibernate.query.criteria;
|
||||
|
||||
import org.hibernate.Incubating;
|
||||
|
||||
import jakarta.persistence.criteria.Expression;
|
||||
|
||||
/**
|
||||
* A special expression for the {@code json_exists} function.
|
||||
* @since 7.0
|
||||
*/
|
||||
@Incubating
|
||||
public interface JpaJsonExistsExpression extends JpaExpression<Boolean> {
|
||||
/**
|
||||
* Get the {@link ErrorBehavior} of this json value expression.
|
||||
*
|
||||
* @return the error behavior
|
||||
*/
|
||||
ErrorBehavior getErrorBehavior();
|
||||
|
||||
/**
|
||||
* Sets the {@link ErrorBehavior#UNSPECIFIED} for this json exists expression.
|
||||
*
|
||||
* @return {@code this} for method chaining
|
||||
*/
|
||||
JpaJsonExistsExpression unspecifiedOnError();
|
||||
/**
|
||||
* Sets the {@link ErrorBehavior#ERROR} for this json exists expression.
|
||||
*
|
||||
* @return {@code this} for method chaining
|
||||
*/
|
||||
JpaJsonExistsExpression errorOnError();
|
||||
/**
|
||||
* Sets the {@link ErrorBehavior#TRUE} for this json exists expression.
|
||||
*
|
||||
* @return {@code this} for method chaining
|
||||
*/
|
||||
JpaJsonExistsExpression trueOnError();
|
||||
/**
|
||||
* Sets the {@link ErrorBehavior#FALSE} for this json exists expression.
|
||||
*
|
||||
* @return {@code this} for method chaining
|
||||
*/
|
||||
JpaJsonExistsExpression falseOnError();
|
||||
|
||||
/**
|
||||
* Passes the given {@link Expression} as value for the parameter with the given name in the JSON path.
|
||||
*
|
||||
* @return {@code this} for method chaining
|
||||
*/
|
||||
JpaJsonExistsExpression passing(String parameterName, Expression<?> expression);
|
||||
|
||||
/**
|
||||
* The behavior of the json value expression when a JSON processing error occurs.
|
||||
*/
|
||||
enum ErrorBehavior {
|
||||
/**
|
||||
* SQL/JDBC error should be raised.
|
||||
*/
|
||||
ERROR,
|
||||
/**
|
||||
* {@code true} should be returned on error.
|
||||
*/
|
||||
TRUE,
|
||||
/**
|
||||
* {@code false} should be returned on error.
|
||||
*/
|
||||
FALSE,
|
||||
/**
|
||||
* Unspecified behavior i.e. the default database behavior.
|
||||
*/
|
||||
UNSPECIFIED
|
||||
}
|
||||
}
|
|
@ -97,6 +97,13 @@ public interface JpaJsonValueExpression<T> extends JpaExpression<T> {
|
|||
*/
|
||||
JpaJsonValueExpression<T> defaultOnEmpty(Expression<?> expression);
|
||||
|
||||
/**
|
||||
* Passes the given {@link Expression} as value for the parameter with the given name in the JSON path.
|
||||
*
|
||||
* @return {@code this} for method chaining
|
||||
*/
|
||||
JpaJsonValueExpression<T> passing(String parameterName, Expression<?> expression);
|
||||
|
||||
/**
|
||||
* The behavior of the json value expression when a JSON processing error occurs.
|
||||
*/
|
||||
|
|
|
@ -38,6 +38,7 @@ import org.hibernate.query.criteria.JpaExpression;
|
|||
import org.hibernate.query.criteria.JpaFunction;
|
||||
import org.hibernate.query.criteria.JpaInPredicate;
|
||||
import org.hibernate.query.criteria.JpaJoin;
|
||||
import org.hibernate.query.criteria.JpaJsonExistsExpression;
|
||||
import org.hibernate.query.criteria.JpaJsonValueExpression;
|
||||
import org.hibernate.query.criteria.JpaListJoin;
|
||||
import org.hibernate.query.criteria.JpaMapJoin;
|
||||
|
@ -3375,6 +3376,18 @@ public class HibernateCriteriaBuilderDelegate implements HibernateCriteriaBuilde
|
|||
return criteriaBuilder.jsonValue( jsonDocument, jsonPath, returningType );
|
||||
}
|
||||
|
||||
@Override
|
||||
@Incubating
|
||||
public JpaJsonExistsExpression jsonExists(Expression<?> jsonDocument, String jsonPath) {
|
||||
return criteriaBuilder.jsonExists( jsonDocument, jsonPath );
|
||||
}
|
||||
|
||||
@Override
|
||||
@Incubating
|
||||
public JpaJsonExistsExpression jsonExists(Expression<?> jsonDocument, Expression<String> jsonPath) {
|
||||
return criteriaBuilder.jsonExists( jsonDocument, jsonPath );
|
||||
}
|
||||
|
||||
@Override
|
||||
@Incubating
|
||||
public JpaExpression<String> jsonObject(Map<?, ? extends Expression<?>> keyValues) {
|
||||
|
|
|
@ -144,6 +144,7 @@ import org.hibernate.query.sqm.tree.expression.SqmExtractUnit;
|
|||
import org.hibernate.query.sqm.tree.expression.SqmFormat;
|
||||
import org.hibernate.query.sqm.tree.expression.SqmFunction;
|
||||
import org.hibernate.query.sqm.tree.expression.SqmHqlNumericLiteral;
|
||||
import org.hibernate.query.sqm.tree.expression.SqmJsonExistsExpression;
|
||||
import org.hibernate.query.sqm.tree.expression.SqmJsonNullBehavior;
|
||||
import org.hibernate.query.sqm.tree.expression.SqmJsonValueExpression;
|
||||
import org.hibernate.query.sqm.tree.expression.SqmLiteral;
|
||||
|
@ -2730,9 +2731,53 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
|
|||
}
|
||||
}
|
||||
}
|
||||
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++ ) {
|
||||
jsonValue.passing(
|
||||
visitIdentifier( identifierContexts.get( i ) ),
|
||||
(SqmExpression<?>) expressionContexts.get( i ).accept( this )
|
||||
);
|
||||
}
|
||||
}
|
||||
return jsonValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SqmExpression<?> visitJsonExistsFunction(HqlParser.JsonExistsFunctionContext ctx) {
|
||||
final SqmExpression<?> jsonDocument = (SqmExpression<?>) ctx.expression( 0 ).accept( this );
|
||||
final SqmExpression<?> jsonPath = (SqmExpression<?>) ctx.expression( 1 ).accept( this );
|
||||
|
||||
final SqmJsonExistsExpression jsonExists = (SqmJsonExistsExpression) getFunctionDescriptor( "json_exists" ).<Boolean>generateSqmExpression(
|
||||
asList( jsonDocument, jsonPath ),
|
||||
null,
|
||||
creationContext.getQueryEngine()
|
||||
);
|
||||
final HqlParser.JsonExistsOnErrorClauseContext subCtx = ctx.jsonExistsOnErrorClause();
|
||||
if ( subCtx != null ) {
|
||||
final TerminalNode firstToken = (TerminalNode) subCtx.getChild( 0 );
|
||||
switch ( firstToken.getSymbol().getType() ) {
|
||||
case HqlParser.ERROR -> jsonExists.errorOnError();
|
||||
case HqlParser.TRUE -> jsonExists.trueOnError();
|
||||
case HqlParser.FALSE -> jsonExists.falseOnError();
|
||||
}
|
||||
}
|
||||
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++ ) {
|
||||
jsonExists.passing(
|
||||
visitIdentifier( identifierContexts.get( i ) ),
|
||||
(SqmExpression<?>) expressionContexts.get( i ).accept( this )
|
||||
);
|
||||
}
|
||||
}
|
||||
return jsonExists;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SqmExpression<?> visitJsonArrayFunction(HqlParser.JsonArrayFunctionContext ctx) {
|
||||
final HqlParser.JsonNullClauseContext subCtx = ctx.jsonNullClause();
|
||||
|
|
|
@ -1,33 +0,0 @@
|
|||
/*
|
||||
* SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
* Copyright Red Hat Inc. and Hibernate Authors
|
||||
*/
|
||||
package org.hibernate.query.internal;
|
||||
|
||||
/**
|
||||
* @author Christian Beikov
|
||||
*/
|
||||
public class QueryLiteralHelper {
|
||||
private QueryLiteralHelper() {
|
||||
// disallow direct instantiation
|
||||
}
|
||||
|
||||
public static String toStringLiteral(String value) {
|
||||
final StringBuilder sb = new StringBuilder( value.length() + 2 );
|
||||
appendStringLiteral( sb, value );
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public static void appendStringLiteral(StringBuilder sb, String value) {
|
||||
sb.append( '\'' );
|
||||
for ( int i = 0; i < value.length(); i++ ) {
|
||||
final char c = value.charAt( i );
|
||||
if ( c == '\'' ) {
|
||||
sb.append( '\'' );
|
||||
}
|
||||
sb.append( c );
|
||||
}
|
||||
sb.append( '\'' );
|
||||
}
|
||||
|
||||
}
|
|
@ -26,6 +26,7 @@ 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;
|
||||
|
@ -44,6 +45,7 @@ import org.hibernate.query.sqm.tree.domain.SqmSetJoin;
|
|||
import org.hibernate.query.sqm.tree.domain.SqmSingularJoin;
|
||||
import org.hibernate.query.sqm.tree.expression.SqmExpression;
|
||||
import org.hibernate.query.sqm.tree.expression.SqmFunction;
|
||||
import org.hibernate.query.sqm.tree.expression.SqmJsonExistsExpression;
|
||||
import org.hibernate.query.sqm.tree.expression.SqmJsonValueExpression;
|
||||
import org.hibernate.query.sqm.tree.expression.SqmModifiedSubQueryExpression;
|
||||
import org.hibernate.query.sqm.tree.expression.SqmTuple;
|
||||
|
@ -629,6 +631,12 @@ public interface NodeBuilder extends HibernateCriteriaBuilder, BindingContext {
|
|||
@Override
|
||||
SqmJsonValueExpression<String> jsonValue(Expression<?> jsonDocument, String jsonPath);
|
||||
|
||||
@Override
|
||||
SqmJsonExistsExpression jsonExists(Expression<?> jsonDocument, Expression<String> jsonPath);
|
||||
|
||||
@Override
|
||||
SqmJsonExistsExpression jsonExists(Expression<?> jsonDocument, String jsonPath);
|
||||
|
||||
@Override
|
||||
SqmExpression<String> jsonArrayWithNulls(Expression<?>... values);
|
||||
|
||||
|
|
|
@ -220,4 +220,13 @@ public interface SqmFunctionDescriptor {
|
|||
* @return an instance of {@link ArgumentsValidator}
|
||||
*/
|
||||
ArgumentsValidator getArgumentsValidator();
|
||||
|
||||
/**
|
||||
* Whether the function renders as a predicate.
|
||||
*
|
||||
* @since 7.0
|
||||
*/
|
||||
default boolean isPredicate() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -120,6 +120,7 @@ import org.hibernate.query.sqm.tree.expression.SqmExpression;
|
|||
import org.hibernate.query.sqm.tree.expression.SqmExtractUnit;
|
||||
import org.hibernate.query.sqm.tree.expression.SqmFormat;
|
||||
import org.hibernate.query.sqm.tree.expression.SqmFunction;
|
||||
import org.hibernate.query.sqm.tree.expression.SqmJsonExistsExpression;
|
||||
import org.hibernate.query.sqm.tree.expression.SqmJsonNullBehavior;
|
||||
import org.hibernate.query.sqm.tree.expression.SqmJsonValueExpression;
|
||||
import org.hibernate.query.sqm.tree.expression.SqmLiteral;
|
||||
|
@ -5333,6 +5334,20 @@ public class SqmCriteriaNodeBuilder implements NodeBuilder, Serializable {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public SqmJsonExistsExpression jsonExists(Expression<?> jsonDocument, String jsonPath) {
|
||||
return jsonExists( jsonDocument, value( jsonPath ) );
|
||||
}
|
||||
|
||||
@Override
|
||||
public SqmJsonExistsExpression jsonExists(Expression<?> jsonDocument, Expression<String> jsonPath) {
|
||||
return (SqmJsonExistsExpression) getFunctionDescriptor( "json_exists" ).<Boolean>generateSqmExpression(
|
||||
asList( (SqmTypedNode<?>) jsonDocument, (SqmTypedNode<?>) jsonPath ),
|
||||
null,
|
||||
queryEngine
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SqmExpression<String> jsonArrayWithNulls(Expression<?>... values) {
|
||||
final var arguments = new ArrayList<SqmTypedNode<?>>( values.length + 1 );
|
||||
|
|
|
@ -289,6 +289,7 @@ import org.hibernate.query.sqm.tree.update.SqmUpdateStatement;
|
|||
import org.hibernate.spi.NavigablePath;
|
||||
import org.hibernate.sql.ast.Clause;
|
||||
import org.hibernate.sql.ast.SqlAstJoinType;
|
||||
import org.hibernate.sql.ast.SqlAstTranslator;
|
||||
import org.hibernate.sql.ast.SqlTreeCreationException;
|
||||
import org.hibernate.sql.ast.SqlTreeCreationLogger;
|
||||
import org.hibernate.sql.ast.internal.TableGroupJoinHelper;
|
||||
|
@ -297,6 +298,7 @@ import org.hibernate.sql.ast.spi.SqlAliasBase;
|
|||
import org.hibernate.sql.ast.spi.SqlAliasBaseConstant;
|
||||
import org.hibernate.sql.ast.spi.SqlAliasBaseGenerator;
|
||||
import org.hibernate.sql.ast.spi.SqlAliasBaseManager;
|
||||
import org.hibernate.sql.ast.spi.SqlAppender;
|
||||
import org.hibernate.sql.ast.spi.SqlAstCreationContext;
|
||||
import org.hibernate.sql.ast.spi.SqlAstCreationState;
|
||||
import org.hibernate.sql.ast.spi.SqlAstProcessingState;
|
||||
|
@ -6439,7 +6441,22 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
|
|||
functionImpliedResultTypeAccess = inferrableTypeAccessStack.getCurrent();
|
||||
inferrableTypeAccessStack.push( () -> null );
|
||||
try {
|
||||
return sqmFunction.convertToSqlAst( this );
|
||||
final Expression expression = sqmFunction.convertToSqlAst( this );
|
||||
if ( sqmFunction.getFunctionDescriptor().isPredicate()
|
||||
&& expression instanceof SelfRenderingExpression selfRenderingExpression) {
|
||||
final BasicType<Boolean> booleanType = getBooleanType();
|
||||
return new CaseSearchedExpression(
|
||||
booleanType,
|
||||
List.of(
|
||||
new CaseSearchedExpression.WhenFragment(
|
||||
new SelfRenderingPredicate( selfRenderingExpression ),
|
||||
new QueryLiteral<>( true, booleanType )
|
||||
)
|
||||
),
|
||||
new QueryLiteral<>( false, booleanType )
|
||||
);
|
||||
}
|
||||
return expression;
|
||||
}
|
||||
finally {
|
||||
inferrableTypeAccessStack.pop();
|
||||
|
@ -8220,14 +8237,27 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
|
|||
);
|
||||
}
|
||||
|
||||
private JdbcMappingContainer getBooleanType() {
|
||||
private BasicType<Boolean> getBooleanType() {
|
||||
return getTypeConfiguration().getBasicTypeForJavaType( Boolean.class );
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object visitBooleanExpressionPredicate(SqmBooleanExpressionPredicate predicate) {
|
||||
inferrableTypeAccessStack.push( this::getBooleanType );
|
||||
final Expression booleanExpression = (Expression) predicate.getBooleanExpression().accept( this );
|
||||
final SqmExpression<Boolean> sqmExpression = predicate.getBooleanExpression();
|
||||
final Expression booleanExpression = (Expression) sqmExpression.accept( this );
|
||||
if ( booleanExpression instanceof CaseSearchedExpression caseExpr
|
||||
&& sqmExpression instanceof SqmFunction<?> sqmFunction
|
||||
&& sqmFunction.getFunctionDescriptor().isPredicate() ) {
|
||||
// Functions that are rendered as predicates are always wrapped,
|
||||
// so the following unwraps the predicate and returns it directly instead of wrapping once more
|
||||
final Predicate sqlPredicate = caseExpr.getWhenFragments().get( 0 ).getPredicate();
|
||||
if ( predicate.isNegated() ) {
|
||||
return new NegatedPredicate( sqlPredicate );
|
||||
}
|
||||
return sqlPredicate;
|
||||
}
|
||||
|
||||
inferrableTypeAccessStack.pop();
|
||||
if ( booleanExpression instanceof SelfRenderingExpression ) {
|
||||
final Predicate sqlPredicate = new SelfRenderingPredicate( (SelfRenderingExpression) booleanExpression );
|
||||
|
|
|
@ -0,0 +1,128 @@
|
|||
/*
|
||||
* 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.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.hibernate.Incubating;
|
||||
import org.hibernate.internal.util.QuotingHelper;
|
||||
import org.hibernate.query.ReturnableType;
|
||||
import org.hibernate.query.sqm.NodeBuilder;
|
||||
import org.hibernate.query.sqm.function.FunctionRenderer;
|
||||
import org.hibernate.query.sqm.function.SelfRenderingSqmFunction;
|
||||
import org.hibernate.query.sqm.function.SqmFunctionDescriptor;
|
||||
import org.hibernate.query.sqm.produce.function.ArgumentsValidator;
|
||||
import org.hibernate.query.sqm.produce.function.FunctionReturnTypeResolver;
|
||||
import org.hibernate.query.sqm.sql.SqmToSqlAstConverter;
|
||||
import org.hibernate.query.sqm.tree.SqmCopyContext;
|
||||
import org.hibernate.query.sqm.tree.SqmTypedNode;
|
||||
import org.hibernate.sql.ast.tree.expression.Expression;
|
||||
import org.hibernate.sql.ast.tree.expression.JsonPathPassingClause;
|
||||
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
/**
|
||||
* Base class for expressions that contain a json path. Maintains a map of expressions for identifiers.
|
||||
*
|
||||
* @since 7.0
|
||||
*/
|
||||
@Incubating
|
||||
public abstract class AbstractSqmJsonPathExpression<T> extends SelfRenderingSqmFunction<T> {
|
||||
|
||||
private @Nullable Map<String, SqmExpression<?>> passingExpressions;
|
||||
|
||||
public AbstractSqmJsonPathExpression(
|
||||
SqmFunctionDescriptor descriptor,
|
||||
FunctionRenderer renderer,
|
||||
List<? extends SqmTypedNode<?>> arguments,
|
||||
@Nullable ReturnableType<T> impliedResultType,
|
||||
@Nullable ArgumentsValidator argumentsValidator,
|
||||
FunctionReturnTypeResolver returnTypeResolver,
|
||||
NodeBuilder nodeBuilder,
|
||||
String name) {
|
||||
super(
|
||||
descriptor,
|
||||
renderer,
|
||||
arguments,
|
||||
impliedResultType,
|
||||
argumentsValidator,
|
||||
returnTypeResolver,
|
||||
nodeBuilder,
|
||||
name
|
||||
);
|
||||
}
|
||||
|
||||
protected AbstractSqmJsonPathExpression(
|
||||
SqmFunctionDescriptor descriptor,
|
||||
FunctionRenderer renderer,
|
||||
List<? extends SqmTypedNode<?>> arguments,
|
||||
@Nullable ReturnableType<T> impliedResultType,
|
||||
@Nullable ArgumentsValidator argumentsValidator,
|
||||
FunctionReturnTypeResolver returnTypeResolver,
|
||||
NodeBuilder nodeBuilder,
|
||||
String name,
|
||||
@Nullable Map<String, SqmExpression<?>> passingExpressions) {
|
||||
super(
|
||||
descriptor,
|
||||
renderer,
|
||||
arguments,
|
||||
impliedResultType,
|
||||
argumentsValidator,
|
||||
returnTypeResolver,
|
||||
nodeBuilder,
|
||||
name
|
||||
);
|
||||
this.passingExpressions = passingExpressions;
|
||||
}
|
||||
|
||||
public Map<String, SqmExpression<?>> getPassingExpressions() {
|
||||
return passingExpressions == null ? Collections.emptyMap() : Collections.unmodifiableMap( passingExpressions );
|
||||
}
|
||||
|
||||
protected void addPassingExpression(String identifier, SqmExpression<?> expression) {
|
||||
if ( passingExpressions == null ) {
|
||||
passingExpressions = new HashMap<>();
|
||||
}
|
||||
passingExpressions.put( identifier, expression );
|
||||
}
|
||||
|
||||
protected Map<String, SqmExpression<?>> copyPassingExpressions(SqmCopyContext context) {
|
||||
if ( passingExpressions == null ) {
|
||||
return null;
|
||||
}
|
||||
final HashMap<String, SqmExpression<?>> copy = new HashMap<>( passingExpressions.size() );
|
||||
for ( Map.Entry<String, SqmExpression<?>> entry : passingExpressions.entrySet() ) {
|
||||
copy.put( entry.getKey(), entry.getValue().copy( context ) );
|
||||
}
|
||||
return copy;
|
||||
}
|
||||
|
||||
protected @Nullable JsonPathPassingClause createJsonPathPassingClause(SqmToSqlAstConverter walker) {
|
||||
if ( passingExpressions == null || passingExpressions.isEmpty() ) {
|
||||
return null;
|
||||
}
|
||||
final HashMap<String, Expression> converted = new HashMap<>( passingExpressions.size() );
|
||||
for ( Map.Entry<String, SqmExpression<?>> entry : passingExpressions.entrySet() ) {
|
||||
converted.put( entry.getKey(), (Expression) entry.getValue().accept( walker ) );
|
||||
}
|
||||
return new JsonPathPassingClause( converted );
|
||||
}
|
||||
|
||||
protected void appendPassingExpressionHqlString(StringBuilder sb) {
|
||||
if ( passingExpressions != null && !passingExpressions.isEmpty() ) {
|
||||
sb.append( " passing " );
|
||||
for ( Map.Entry<String, SqmExpression<?>> entry : passingExpressions.entrySet() ) {
|
||||
entry.getValue().appendHqlString( sb );
|
||||
sb.append( " as " );
|
||||
QuotingHelper.appendDoubleQuoteEscapedString( sb, entry.getKey() );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,207 @@
|
|||
/*
|
||||
* 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.JpaJsonExistsExpression;
|
||||
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.JsonExistsErrorBehavior;
|
||||
import org.hibernate.sql.ast.tree.expression.JsonPathPassingClause;
|
||||
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
/**
|
||||
* Special expression for the json_exists function that also captures special syntax elements like error behavior and passing variables.
|
||||
*
|
||||
* @since 7.0
|
||||
*/
|
||||
@Incubating
|
||||
public class SqmJsonExistsExpression extends AbstractSqmJsonPathExpression<Boolean> implements JpaJsonExistsExpression {
|
||||
private @Nullable ErrorBehavior errorBehavior;
|
||||
|
||||
public SqmJsonExistsExpression(
|
||||
SqmFunctionDescriptor descriptor,
|
||||
FunctionRenderer renderer,
|
||||
List<? extends SqmTypedNode<?>> arguments,
|
||||
@Nullable ReturnableType<Boolean> impliedResultType,
|
||||
@Nullable ArgumentsValidator argumentsValidator,
|
||||
FunctionReturnTypeResolver returnTypeResolver,
|
||||
NodeBuilder nodeBuilder,
|
||||
String name) {
|
||||
super(
|
||||
descriptor,
|
||||
renderer,
|
||||
arguments,
|
||||
impliedResultType,
|
||||
argumentsValidator,
|
||||
returnTypeResolver,
|
||||
nodeBuilder,
|
||||
name
|
||||
);
|
||||
}
|
||||
|
||||
private SqmJsonExistsExpression(
|
||||
SqmFunctionDescriptor descriptor,
|
||||
FunctionRenderer renderer,
|
||||
List<? extends SqmTypedNode<?>> arguments,
|
||||
@Nullable ReturnableType<Boolean> impliedResultType,
|
||||
@Nullable ArgumentsValidator argumentsValidator,
|
||||
FunctionReturnTypeResolver returnTypeResolver,
|
||||
NodeBuilder nodeBuilder,
|
||||
String name,
|
||||
@Nullable Map<String, SqmExpression<?>> passingExpressions,
|
||||
@Nullable ErrorBehavior errorBehavior) {
|
||||
super(
|
||||
descriptor,
|
||||
renderer,
|
||||
arguments,
|
||||
impliedResultType,
|
||||
argumentsValidator,
|
||||
returnTypeResolver,
|
||||
nodeBuilder,
|
||||
name,
|
||||
passingExpressions
|
||||
);
|
||||
this.errorBehavior = errorBehavior;
|
||||
}
|
||||
|
||||
public SqmJsonExistsExpression copy(SqmCopyContext context) {
|
||||
final SqmJsonExistsExpression 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 SqmJsonExistsExpression(
|
||||
getFunctionDescriptor(),
|
||||
getFunctionRenderer(),
|
||||
arguments,
|
||||
getImpliedResultType(),
|
||||
getArgumentsValidator(),
|
||||
getReturnTypeResolver(),
|
||||
nodeBuilder(),
|
||||
getFunctionName(),
|
||||
copyPassingExpressions( context ),
|
||||
errorBehavior
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ErrorBehavior getErrorBehavior() {
|
||||
return errorBehavior;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SqmJsonExistsExpression unspecifiedOnError() {
|
||||
this.errorBehavior = ErrorBehavior.UNSPECIFIED;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SqmJsonExistsExpression errorOnError() {
|
||||
this.errorBehavior = ErrorBehavior.ERROR;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SqmJsonExistsExpression trueOnError() {
|
||||
this.errorBehavior = ErrorBehavior.TRUE;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SqmJsonExistsExpression falseOnError() {
|
||||
this.errorBehavior = ErrorBehavior.FALSE;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SqmJsonExistsExpression 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 );
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
return new SelfRenderingFunctionSqlAstExpression(
|
||||
getFunctionName(),
|
||||
getFunctionRenderer(),
|
||||
arguments,
|
||||
resultType,
|
||||
resultType == null ? null : getMappingModelExpressible( walker, resultType, arguments )
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void appendHqlString(StringBuilder sb) {
|
||||
sb.append( "json_exists(" );
|
||||
getArguments().get( 0 ).appendHqlString( sb );
|
||||
sb.append( ',' );
|
||||
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;
|
||||
}
|
||||
}
|
||||
sb.append( ')' );
|
||||
}
|
||||
}
|
|
@ -8,6 +8,7 @@ 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;
|
||||
|
@ -16,7 +17,6 @@ import org.hibernate.query.criteria.JpaJsonValueExpression;
|
|||
import org.hibernate.query.sqm.NodeBuilder;
|
||||
import org.hibernate.query.sqm.function.FunctionRenderer;
|
||||
import org.hibernate.query.sqm.function.SelfRenderingFunctionSqlAstExpression;
|
||||
import org.hibernate.query.sqm.function.SelfRenderingSqmFunction;
|
||||
import org.hibernate.query.sqm.function.SqmFunctionDescriptor;
|
||||
import org.hibernate.query.sqm.produce.function.ArgumentsValidator;
|
||||
import org.hibernate.query.sqm.produce.function.FunctionReturnTypeResolver;
|
||||
|
@ -25,6 +25,7 @@ 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.JsonValueEmptyBehavior;
|
||||
import org.hibernate.sql.ast.tree.expression.JsonValueErrorBehavior;
|
||||
|
||||
|
@ -36,7 +37,7 @@ import org.checkerframework.checker.nullness.qual.Nullable;
|
|||
* @since 7.0
|
||||
*/
|
||||
@Incubating
|
||||
public class SqmJsonValueExpression<T> extends SelfRenderingSqmFunction<T> implements JpaJsonValueExpression<T> {
|
||||
public class SqmJsonValueExpression<T> extends AbstractSqmJsonPathExpression<T> implements JpaJsonValueExpression<T> {
|
||||
private @Nullable ErrorBehavior errorBehavior;
|
||||
private SqmExpression<T> errorDefaultExpression;
|
||||
private @Nullable EmptyBehavior emptyBehavior;
|
||||
|
@ -72,6 +73,7 @@ public class SqmJsonValueExpression<T> extends SelfRenderingSqmFunction<T> imple
|
|||
FunctionReturnTypeResolver returnTypeResolver,
|
||||
NodeBuilder nodeBuilder,
|
||||
String name,
|
||||
@Nullable Map<String, SqmExpression<?>> passingExpressions,
|
||||
@Nullable ErrorBehavior errorBehavior,
|
||||
SqmExpression<T> errorDefaultExpression,
|
||||
@Nullable EmptyBehavior emptyBehavior,
|
||||
|
@ -84,7 +86,8 @@ public class SqmJsonValueExpression<T> extends SelfRenderingSqmFunction<T> imple
|
|||
argumentsValidator,
|
||||
returnTypeResolver,
|
||||
nodeBuilder,
|
||||
name
|
||||
name,
|
||||
passingExpressions
|
||||
);
|
||||
this.errorBehavior = errorBehavior;
|
||||
this.errorDefaultExpression = errorDefaultExpression;
|
||||
|
@ -112,6 +115,7 @@ public class SqmJsonValueExpression<T> extends SelfRenderingSqmFunction<T> imple
|
|||
getReturnTypeResolver(),
|
||||
nodeBuilder(),
|
||||
getFunctionName(),
|
||||
copyPassingExpressions( context ),
|
||||
errorBehavior,
|
||||
errorDefaultExpression == null ? null : errorDefaultExpression.copy( context ),
|
||||
emptyBehavior,
|
||||
|
@ -141,28 +145,28 @@ public class SqmJsonValueExpression<T> extends SelfRenderingSqmFunction<T> imple
|
|||
}
|
||||
|
||||
@Override
|
||||
public JpaJsonValueExpression<T> unspecifiedOnError() {
|
||||
public SqmJsonValueExpression<T> unspecifiedOnError() {
|
||||
this.errorBehavior = ErrorBehavior.UNSPECIFIED;
|
||||
this.errorDefaultExpression = null;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JpaJsonValueExpression<T> errorOnError() {
|
||||
public SqmJsonValueExpression<T> errorOnError() {
|
||||
this.errorBehavior = ErrorBehavior.ERROR;
|
||||
this.errorDefaultExpression = null;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JpaJsonValueExpression<T> nullOnError() {
|
||||
public SqmJsonValueExpression<T> nullOnError() {
|
||||
this.errorBehavior = ErrorBehavior.NULL;
|
||||
this.errorDefaultExpression = null;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JpaJsonValueExpression<T> defaultOnError(jakarta.persistence.criteria.Expression<?> expression) {
|
||||
public SqmJsonValueExpression<T> defaultOnError(jakarta.persistence.criteria.Expression<?> expression) {
|
||||
this.errorBehavior = ErrorBehavior.DEFAULT;
|
||||
//noinspection unchecked
|
||||
this.errorDefaultExpression = (SqmExpression<T>) expression;
|
||||
|
@ -170,34 +174,42 @@ public class SqmJsonValueExpression<T> extends SelfRenderingSqmFunction<T> imple
|
|||
}
|
||||
|
||||
@Override
|
||||
public JpaJsonValueExpression<T> unspecifiedOnEmpty() {
|
||||
public SqmJsonValueExpression<T> unspecifiedOnEmpty() {
|
||||
this.errorBehavior = ErrorBehavior.UNSPECIFIED;
|
||||
this.errorDefaultExpression = null;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JpaJsonValueExpression<T> errorOnEmpty() {
|
||||
public SqmJsonValueExpression<T> errorOnEmpty() {
|
||||
this.emptyBehavior = EmptyBehavior.ERROR;
|
||||
this.emptyDefaultExpression = null;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JpaJsonValueExpression<T> nullOnEmpty() {
|
||||
public SqmJsonValueExpression<T> nullOnEmpty() {
|
||||
this.emptyBehavior = EmptyBehavior.NULL;
|
||||
this.emptyDefaultExpression = null;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JpaJsonValueExpression<T> defaultOnEmpty(jakarta.persistence.criteria.Expression<?> expression) {
|
||||
public SqmJsonValueExpression<T> defaultOnEmpty(jakarta.persistence.criteria.Expression<?> expression) {
|
||||
this.emptyBehavior = EmptyBehavior.DEFAULT;
|
||||
//noinspection unchecked
|
||||
this.emptyDefaultExpression = (SqmExpression<T>) expression;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SqmJsonValueExpression<T> 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 );
|
||||
|
@ -206,6 +218,10 @@ public class SqmJsonValueExpression<T> extends SelfRenderingSqmFunction<T> imple
|
|||
if ( validator != null ) {
|
||||
validator.validateSqlTypes( arguments, getFunctionName() );
|
||||
}
|
||||
final JsonPathPassingClause jsonPathPassingClause = createJsonPathPassingClause( walker );
|
||||
if ( jsonPathPassingClause != null ) {
|
||||
arguments.add( jsonPathPassingClause );
|
||||
}
|
||||
if ( errorBehavior != null ) {
|
||||
switch ( errorBehavior ) {
|
||||
case NULL:
|
||||
|
@ -252,6 +268,7 @@ public class SqmJsonValueExpression<T> extends SelfRenderingSqmFunction<T> imple
|
|||
sb.append( ',' );
|
||||
getArguments().get( 1 ).appendHqlString( sb );
|
||||
|
||||
appendPassingExpressionHqlString( sb );
|
||||
if ( getArguments().size() > 2 ) {
|
||||
sb.append( " returning " );
|
||||
getArguments().get( 2 ).appendHqlString( sb );
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
*/
|
||||
package org.hibernate.query.sqm.tree.expression;
|
||||
|
||||
import org.hibernate.query.internal.QueryLiteralHelper;
|
||||
import org.hibernate.internal.util.QuotingHelper;
|
||||
import org.hibernate.query.sqm.NodeBuilder;
|
||||
import org.hibernate.query.sqm.SemanticQueryWalker;
|
||||
import org.hibernate.query.sqm.SqmExpressible;
|
||||
|
@ -82,7 +82,7 @@ public class SqmLiteral<T> extends AbstractSqmExpression<T> {
|
|||
else {
|
||||
final String string = javaType.toString( value );
|
||||
if ( javaType.getJavaTypeClass() == String.class ) {
|
||||
QueryLiteralHelper.appendStringLiteral( sb, string );
|
||||
QuotingHelper.appendSingleQuoteEscapedString( sb, string );
|
||||
}
|
||||
else {
|
||||
sb.append( string );
|
||||
|
|
|
@ -38,6 +38,7 @@ import org.hibernate.engine.spi.SessionImplementor;
|
|||
import org.hibernate.engine.spi.SharedSessionContractImplementor;
|
||||
import org.hibernate.internal.FilterJdbcParameter;
|
||||
import org.hibernate.internal.util.MathHelper;
|
||||
import org.hibernate.internal.util.QuotingHelper;
|
||||
import org.hibernate.internal.util.StringHelper;
|
||||
import org.hibernate.internal.util.collections.CollectionHelper;
|
||||
import org.hibernate.internal.util.collections.Stack;
|
||||
|
@ -119,6 +120,7 @@ import org.hibernate.sql.ast.tree.expression.Every;
|
|||
import org.hibernate.sql.ast.tree.expression.Expression;
|
||||
import org.hibernate.sql.ast.tree.expression.ExtractUnit;
|
||||
import org.hibernate.sql.ast.tree.expression.Format;
|
||||
import org.hibernate.sql.ast.tree.expression.FunctionExpression;
|
||||
import org.hibernate.sql.ast.tree.expression.JdbcLiteral;
|
||||
import org.hibernate.sql.ast.tree.expression.JdbcParameter;
|
||||
import org.hibernate.sql.ast.tree.expression.Literal;
|
||||
|
@ -548,6 +550,16 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
|
|||
sqlBuffer.append( value );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void appendDoubleQuoteEscapedString(String value) {
|
||||
QuotingHelper.appendDoubleQuoteEscapedString( sqlBuffer, value );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void appendSingleQuoteEscapedString(String value) {
|
||||
QuotingHelper.appendSingleQuoteEscapedString( sqlBuffer, value );
|
||||
}
|
||||
|
||||
@Override
|
||||
public Appendable append(CharSequence csq) {
|
||||
sqlBuffer.append( csq );
|
||||
|
@ -680,6 +692,20 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
|
|||
}
|
||||
return (R) getParameterBindValue( (JdbcParameter) ( (SqmParameterInterpretation) expression).getResolvedExpression() );
|
||||
}
|
||||
else if ( expression instanceof FunctionExpression functionExpression ) {
|
||||
if ( "concat".equals( functionExpression.getFunctionName() ) ) {
|
||||
final List<? extends SqlAstNode> arguments = functionExpression.getArguments();
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
for ( SqlAstNode argument : arguments ) {
|
||||
final Object argumentLiteral = interpretExpression( (Expression) argument, jdbcParameterBindings );
|
||||
if ( argumentLiteral == null ) {
|
||||
return null;
|
||||
}
|
||||
sb.append( argumentLiteral );
|
||||
}
|
||||
return (R) sb.toString();
|
||||
}
|
||||
}
|
||||
throw new UnsupportedOperationException( "Can't interpret expression: " + expression );
|
||||
}
|
||||
|
||||
|
|
|
@ -4,6 +4,8 @@
|
|||
*/
|
||||
package org.hibernate.sql.ast.spi;
|
||||
|
||||
import org.hibernate.internal.util.QuotingHelper;
|
||||
|
||||
/**
|
||||
* Access to appending SQL fragments to an in-flight buffer
|
||||
*
|
||||
|
@ -44,6 +46,18 @@ public interface SqlAppender extends Appendable {
|
|||
appendSql( String.valueOf( value ) );
|
||||
}
|
||||
|
||||
default void appendDoubleQuoteEscapedString(String value) {
|
||||
final StringBuilder sb = new StringBuilder( value.length() + 2 );
|
||||
QuotingHelper.appendDoubleQuoteEscapedString( sb, value );
|
||||
appendSql( sb.toString() );
|
||||
}
|
||||
|
||||
default void appendSingleQuoteEscapedString(String value) {
|
||||
final StringBuilder sb = new StringBuilder( value.length() + 2 );
|
||||
QuotingHelper.appendSingleQuoteEscapedString( sb, value );
|
||||
appendSql( sb.toString() );
|
||||
}
|
||||
|
||||
default Appendable append(CharSequence csq) {
|
||||
appendSql( csq.toString() );
|
||||
return this;
|
||||
|
|
|
@ -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 JsonExistsErrorBehavior implements SqlAstNode {
|
||||
TRUE,
|
||||
FALSE,
|
||||
ERROR;
|
||||
|
||||
@Override
|
||||
public void accept(SqlAstWalker sqlTreeWalker) {
|
||||
throw new UnsupportedOperationException("JsonExistsErrorBehavior doesn't support walking");
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* 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 java.util.Map;
|
||||
|
||||
import org.hibernate.sql.ast.SqlAstWalker;
|
||||
import org.hibernate.sql.ast.tree.SqlAstNode;
|
||||
|
||||
/**
|
||||
* @since 7.0
|
||||
*/
|
||||
public class JsonPathPassingClause implements SqlAstNode {
|
||||
|
||||
private final Map<String, Expression> passingExpressions;
|
||||
|
||||
public JsonPathPassingClause(Map<String, Expression> passingExpressions) {
|
||||
this.passingExpressions = passingExpressions;
|
||||
}
|
||||
|
||||
public Map<String, Expression> getPassingExpressions() {
|
||||
return passingExpressions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void accept(SqlAstWalker sqlTreeWalker) {
|
||||
throw new UnsupportedOperationException("JsonPathPassingClause doesn't support walking");
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,102 @@
|
|||
/*
|
||||
* 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 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.SupportsJsonExists.class)
|
||||
public class JsonExistsTest {
|
||||
|
||||
@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-exists-example[]
|
||||
List<Boolean> results = em.createQuery( "select json_exists(e.json, '$.theString') from EntityWithJson e", Boolean.class )
|
||||
.getResultList();
|
||||
//end::hql-json-exists-example[]
|
||||
assertEquals( 1, results.size() );
|
||||
} );
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPassing(SessionFactoryScope scope) {
|
||||
scope.inSession( em -> {
|
||||
//tag::hql-json-exists-passing-example[]
|
||||
List<Boolean> results = em.createQuery( "select json_exists(e.json, '$.theArray[$idx]' passing 1 as idx) from EntityWithJson e", Boolean.class )
|
||||
.getResultList();
|
||||
//end::hql-json-exists-passing-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-exists-on-error-example[]
|
||||
em.createQuery( "select json_exists('invalidJson', '$.theInt' error on error) from EntityWithJson e")
|
||||
.getResultList();
|
||||
//end::hql-json-exists-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;
|
||||
}
|
||||
}
|
||||
} );
|
||||
}
|
||||
|
||||
}
|
|
@ -71,6 +71,17 @@ public class JsonValueTest {
|
|||
} );
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPassing(SessionFactoryScope scope) {
|
||||
scope.inSession( em -> {
|
||||
//tag::hql-json-value-passing-example[]
|
||||
List<Tuple> results = em.createQuery( "select json_value(e.json, '$.theArray[$idx]' passing 1 as idx) from EntityWithJson e", Tuple.class )
|
||||
.getResultList();
|
||||
//end::hql-json-value-passing-example[]
|
||||
assertEquals( 1, results.size() );
|
||||
} );
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReturning(SessionFactoryScope scope) {
|
||||
scope.inSession( em -> {
|
||||
|
@ -91,7 +102,7 @@ public class JsonValueTest {
|
|||
em.createQuery( "select json_value('invalidJson', '$.theInt' error on error) from EntityWithJson e")
|
||||
.getResultList();
|
||||
//end::hql-json-value-on-error-example[]
|
||||
fail("error clause should fail because of invalid json path");
|
||||
fail("error clause should fail because of invalid json document");
|
||||
}
|
||||
catch ( HibernateException e ) {
|
||||
if ( !( e instanceof JDBCException ) && !( e instanceof ExecutionException ) ) {
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
*/
|
||||
package org.hibernate.orm.test.query.hql;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
@ -101,6 +100,20 @@ public class JsonFunctionTests {
|
|||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsJsonValue.class)
|
||||
public void testJsonValueExpression(SessionFactoryScope scope) {
|
||||
scope.inTransaction(
|
||||
session -> {
|
||||
Tuple tuple = session.createQuery(
|
||||
"select json_value('{\"theArray\":[1,10]}', '$.theArray[$idx]' passing :idx as idx) ",
|
||||
Tuple.class
|
||||
).setParameter( "idx", 0 ).getSingleResult();
|
||||
assertEquals( "1", tuple.get( 0 ) );
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsJsonArray.class)
|
||||
public void testJsonArray(SessionFactoryScope scope) {
|
||||
|
@ -209,6 +222,29 @@ public class JsonFunctionTests {
|
|||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsJsonExists.class)
|
||||
public void testJsonExists(SessionFactoryScope scope) {
|
||||
scope.inTransaction(
|
||||
session -> {
|
||||
Tuple tuple = session.createQuery(
|
||||
"select " +
|
||||
"json_exists(e.json, '$.theUnknown'), " +
|
||||
"json_exists(e.json, '$.theInt'), " +
|
||||
"json_exists(e.json, '$.theArray[0]'), " +
|
||||
"json_exists(e.json, '$.theArray[$idx]' passing :idx as idx) " +
|
||||
"from JsonHolder e " +
|
||||
"where e.id = 1L",
|
||||
Tuple.class
|
||||
).setParameter( "idx", 3 ).getSingleResult();
|
||||
assertEquals( false, tuple.get( 0 ) );
|
||||
assertEquals( true, tuple.get( 1 ) );
|
||||
assertEquals( true, tuple.get( 2 ) );
|
||||
assertEquals( false, tuple.get( 3 ) );
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private static final ObjectMapper MAPPER = new ObjectMapper();
|
||||
|
||||
private static Map<String, Object> parseObject(String json) {
|
||||
|
@ -230,22 +266,6 @@ public class JsonFunctionTests {
|
|||
}
|
||||
}
|
||||
|
||||
private static double[] parseDoubleArray( String s ) {
|
||||
final List<Double> list = new ArrayList<>();
|
||||
int startIndex = 1;
|
||||
int commaIndex;
|
||||
while ( (commaIndex = s.indexOf(',', startIndex)) != -1 ) {
|
||||
list.add( Double.parseDouble( s.substring( startIndex, commaIndex ) ) );
|
||||
startIndex = commaIndex + 1;
|
||||
}
|
||||
list.add( Double.parseDouble( s.substring( startIndex, s.length() - 1 ) ) );
|
||||
double[] array = new double[list.size()];
|
||||
for ( int i = 0; i < list.size(); i++ ) {
|
||||
array[i] = list.get( i );
|
||||
}
|
||||
return array;
|
||||
}
|
||||
|
||||
@Entity(name = "JsonHolder")
|
||||
public static class JsonHolder {
|
||||
@Id
|
||||
|
|
|
@ -30,8 +30,8 @@ import org.hibernate.envers.query.criteria.AuditProperty;
|
|||
import org.hibernate.envers.query.criteria.internal.CriteriaTools;
|
||||
import org.hibernate.envers.query.order.NullPrecedence;
|
||||
import org.hibernate.envers.tools.Pair;
|
||||
import org.hibernate.internal.util.QuotingHelper;
|
||||
import org.hibernate.query.Query;
|
||||
import org.hibernate.query.internal.QueryLiteralHelper;
|
||||
import org.hibernate.type.BasicType;
|
||||
|
||||
/**
|
||||
|
@ -365,10 +365,10 @@ public class QueryBuilder {
|
|||
final Pair<String, String> fragment = fragmentIterator.next();
|
||||
sb.append( OrderByFragmentFunction.FUNCTION_NAME ).append( '(' );
|
||||
// The first argument is the sqm alias of the from node
|
||||
QueryLiteralHelper.appendStringLiteral( sb, fragment.getFirst() );
|
||||
QuotingHelper.appendSingleQuoteEscapedString( sb, fragment.getFirst() );
|
||||
sb.append( ", " );
|
||||
// The second argument is the collection role that contains the order by fragment
|
||||
QueryLiteralHelper.appendStringLiteral( sb, fragment.getSecond() );
|
||||
QuotingHelper.appendSingleQuoteEscapedString( sb, fragment.getSecond() );
|
||||
sb.append( ')' );
|
||||
if ( fragmentIterator.hasNext() ) {
|
||||
sb.append( ", " );
|
||||
|
|
|
@ -732,6 +732,12 @@ abstract public class DialectFeatureChecks {
|
|||
}
|
||||
}
|
||||
|
||||
public static class SupportsJsonExists implements DialectFeatureCheck {
|
||||
public boolean apply(Dialect dialect) {
|
||||
return definesFunction( dialect, "json_exists" );
|
||||
}
|
||||
}
|
||||
|
||||
public static class SupportsJsonArray implements DialectFeatureCheck {
|
||||
public boolean apply(Dialect dialect) {
|
||||
return definesFunction( dialect, "json_array" );
|
||||
|
|
Loading…
Reference in New Issue