HHH-18496 Add json_objectagg
This commit is contained in:
parent
c58485c4ef
commit
59ae75bb52
|
@ -1635,6 +1635,7 @@ The following functions deal with SQL JSON types, which are not supported on eve
|
|||
| `json_exists()` | Checks if a JSON path exists in a JSON document
|
||||
| `json_query()` | Queries non-scalar values by JSON path in a JSON document
|
||||
| `json_arrayagg()` | Creates a JSON array by aggregating values
|
||||
| `json_objectagg()` | Creates a JSON object by aggregating values
|
||||
|===
|
||||
|
||||
|
||||
|
@ -1963,6 +1964,52 @@ include::{json-example-dir-hql}/JsonArrayAggregateTest.java[tags=hql-json-arraya
|
|||
----
|
||||
====
|
||||
|
||||
[[hql-json-objectagg-function]]
|
||||
===== `json_objectagg()`
|
||||
|
||||
Creates a JSON object by aggregating values.
|
||||
|
||||
[[hql-json-arrayagg-bnf]]
|
||||
[source, antlrv4, indent=0]
|
||||
----
|
||||
include::{extrasdir}/json_objectagg_bnf.txt[]
|
||||
----
|
||||
|
||||
The arguments represent the key and the value to be aggregated to the JSON object,
|
||||
separated by the `value` keyword or a `:` (colon).
|
||||
|
||||
[[hql-json-objectagg-example]]
|
||||
====
|
||||
[source, java, indent=0]
|
||||
----
|
||||
include::{json-example-dir-hql}/JsonObjectAggregateTest.java[tags=hql-json-objectagg-example]
|
||||
----
|
||||
====
|
||||
|
||||
Although database dependent, usually `null` values are `absent` in the resulting JSON array.
|
||||
To retain `null` elements, use the `null on null` clause.
|
||||
|
||||
[[hql-json-objectagg-null-example]]
|
||||
====
|
||||
[source, java, indent=0]
|
||||
----
|
||||
include::{json-example-dir-hql}/JsonObjectAggregateTest.java[tags=hql-json-objectagg-null-example]
|
||||
----
|
||||
====
|
||||
|
||||
Duplicate keys usually are retained in the resulting string.
|
||||
Use `with unique keys` to specify that the encounter of a duplicate key should cause an error.
|
||||
|
||||
[[hql-json-objectagg-unique-keys-example]]
|
||||
====
|
||||
[source, java, indent=0]
|
||||
----
|
||||
include::{json-example-dir-hql}/JsonObjectAggregateTest.java[tags=hql-json-objectagg-unique-keys-example]
|
||||
----
|
||||
====
|
||||
|
||||
WARNING: Some databases like e.g. MySQL, SAP HANA, DB2 and SQL Server do not support raising an error on duplicate keys.
|
||||
|
||||
[[hql-user-defined-functions]]
|
||||
==== Native and user-defined functions
|
||||
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
"json_objectagg(" expressionOrPredicate ("value"|":") expressionOrPredicate jsonNullClause? uniqueKeysClause? ")" filterClause?
|
||||
|
||||
jsonNullClause
|
||||
: ("absent"|"null") "on null"
|
||||
;
|
||||
|
||||
uniqueKeysClause
|
||||
: ("with"|"without") "unique keys"
|
||||
;
|
|
@ -502,9 +502,12 @@ public class CockroachLegacyDialect extends Dialect {
|
|||
functionFactory.arrayToString_postgresql();
|
||||
|
||||
functionFactory.jsonValue_cockroachdb();
|
||||
functionFactory.jsonQuery_cockroachdb();
|
||||
functionFactory.jsonExists_cockroachdb();
|
||||
functionFactory.jsonObject_postgresql();
|
||||
functionFactory.jsonExists_postgresql();
|
||||
functionFactory.jsonArray_postgresql();
|
||||
functionFactory.jsonArrayAgg_postgresql( false );
|
||||
functionFactory.jsonObjectAgg_postgresql( false );
|
||||
|
||||
// Postgres uses # instead of ^ for XOR
|
||||
functionContributions.getFunctionRegistry().patternDescriptorBuilder( "bitxor", "(?1#?2)" )
|
||||
|
|
|
@ -437,6 +437,7 @@ public class DB2LegacyDialect extends Dialect {
|
|||
functionFactory.jsonObject_db2();
|
||||
functionFactory.jsonArray_db2();
|
||||
functionFactory.jsonArrayAgg_db2();
|
||||
functionFactory.jsonObjectAgg_db2();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -407,6 +407,7 @@ public class H2LegacyDialect extends Dialect {
|
|||
functionFactory.jsonQuery_h2();
|
||||
functionFactory.jsonExists_h2();
|
||||
functionFactory.jsonArrayAgg_h2();
|
||||
functionFactory.jsonObjectAgg_h2();
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
|
|
@ -274,6 +274,7 @@ public class HSQLLegacyDialect extends Dialect {
|
|||
functionFactory.jsonObject_hsqldb();
|
||||
functionFactory.jsonArray_hsqldb();
|
||||
functionFactory.jsonArrayAgg_hsqldb();
|
||||
functionFactory.jsonObjectAgg_h2();
|
||||
}
|
||||
|
||||
//trim() requires parameters to be cast when used as trim character
|
||||
|
|
|
@ -94,6 +94,7 @@ public class MariaDBLegacyDialect extends MySQLLegacyDialect {
|
|||
commonFunctionFactory.jsonArray_mariadb();
|
||||
commonFunctionFactory.jsonQuery_mariadb();
|
||||
commonFunctionFactory.jsonArrayAgg_mariadb();
|
||||
commonFunctionFactory.jsonObjectAgg_mariadb();
|
||||
if ( getVersion().isSameOrAfter( 10, 3, 3 ) ) {
|
||||
commonFunctionFactory.inverseDistributionOrderedSetAggregates_windowEmulation();
|
||||
functionContributions.getFunctionRegistry().patternDescriptorBuilder( "median", "median(?1) over ()" )
|
||||
|
|
|
@ -659,6 +659,7 @@ public class MySQLLegacyDialect extends Dialect {
|
|||
functionFactory.jsonObject_mysql();
|
||||
functionFactory.jsonArray_mysql();
|
||||
functionFactory.jsonArrayAgg_mysql();
|
||||
functionFactory.jsonObjectAgg_mysql();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -638,7 +638,8 @@ public class PostgreSQLLegacyDialect extends Dialect {
|
|||
functionFactory.jsonExists();
|
||||
functionFactory.jsonObject();
|
||||
functionFactory.jsonArray();
|
||||
functionFactory.jsonArrayAgg();
|
||||
functionFactory.jsonArrayAgg_postgresql( true );
|
||||
functionFactory.jsonObjectAgg_postgresql( true );
|
||||
}
|
||||
else {
|
||||
functionFactory.jsonValue_postgresql();
|
||||
|
@ -647,12 +648,14 @@ public class PostgreSQLLegacyDialect extends Dialect {
|
|||
if ( getVersion().isSameOrAfter( 16 ) ) {
|
||||
functionFactory.jsonObject();
|
||||
functionFactory.jsonArray();
|
||||
functionFactory.jsonArrayAgg();
|
||||
functionFactory.jsonArrayAgg_postgresql( true );
|
||||
functionFactory.jsonObjectAgg_postgresql( true );
|
||||
}
|
||||
else {
|
||||
functionFactory.jsonObject_postgresql();
|
||||
functionFactory.jsonArray_postgresql();
|
||||
functionFactory.jsonArrayAgg_postgresql();
|
||||
functionFactory.jsonArrayAgg_postgresql( false );
|
||||
functionFactory.jsonObjectAgg_postgresql( false );
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -410,6 +410,7 @@ public class SQLServerLegacyDialect extends AbstractTransactSQLDialect {
|
|||
if ( getVersion().isSameOrAfter( 14 ) ) {
|
||||
functionFactory.listagg_stringAggWithinGroup( "varchar(max)" );
|
||||
functionFactory.jsonArrayAgg_sqlserver();
|
||||
functionFactory.jsonObjectAgg_sqlserver();
|
||||
}
|
||||
if ( getVersion().isSameOrAfter( 16 ) ) {
|
||||
functionFactory.leastGreatest();
|
||||
|
|
|
@ -228,6 +228,7 @@ JSON_ARRAY : [jJ] [sS] [oO] [nN] '_' [aA] [rR] [rR] [aA] [yY];
|
|||
JSON_ARRAYAGG : [jJ] [sS] [oO] [nN] '_' [aA] [rR] [rR] [aA] [yY] [aA] [gG] [gG];
|
||||
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_OBJECTAGG : [jJ] [sS] [oO] [nN] '_' [oO] [bB] [jJ] [eE] [cC] [tT] [aA] [gG] [gG];
|
||||
JSON_QUERY : [jJ] [sS] [oO] [nN] '_' [qQ] [uU] [eE] [rR] [yY];
|
||||
JSON_VALUE : [jJ] [sS] [oO] [nN] '_' [vV] [aA] [lL] [uU] [eE];
|
||||
KEY : [kK] [eE] [yY];
|
||||
|
@ -316,6 +317,7 @@ TYPE : [tT] [yY] [pP] [eE];
|
|||
UNBOUNDED : [uU] [nN] [bB] [oO] [uU] [nN] [dD] [eE] [dD];
|
||||
UNCONDITIONAL : [uU] [nN] [cC] [oO] [nN] [dD] [iI] [tT] [iI] [oO] [nN] [aA] [lL];
|
||||
UNION : [uU] [nN] [iI] [oO] [nN];
|
||||
UNIQUE : [uU] [nN] [iI] [qQ] [uU] [eE];
|
||||
UPDATE : [uU] [pP] [dD] [aA] [tT] [eE];
|
||||
USING : [uU] [sS] [iI] [nN] [gG];
|
||||
VALUE : [vV] [aA] [lL] [uU] [eE];
|
||||
|
|
|
@ -1628,6 +1628,7 @@ jsonFunction
|
|||
| jsonQueryFunction
|
||||
| jsonValueFunction
|
||||
| jsonArrayAggFunction
|
||||
| jsonObjectAggFunction
|
||||
;
|
||||
|
||||
/**
|
||||
|
@ -1704,6 +1705,17 @@ jsonArrayAggFunction
|
|||
: JSON_ARRAYAGG LEFT_PAREN expressionOrPredicate jsonNullClause? orderByClause? RIGHT_PAREN filterClause?
|
||||
;
|
||||
|
||||
/**
|
||||
* The 'json_objectagg()' function
|
||||
*/
|
||||
jsonObjectAggFunction
|
||||
: JSON_OBJECTAGG LEFT_PAREN KEY? expressionOrPredicate (VALUE|COLON) expressionOrPredicate jsonNullClause? jsonUniqueKeysClause? RIGHT_PAREN filterClause?
|
||||
;
|
||||
|
||||
jsonUniqueKeysClause
|
||||
: (WITH|WITHOUT) UNIQUE KEYS
|
||||
;
|
||||
|
||||
/**
|
||||
* Support for "soft" keywords which may be used as identifiers
|
||||
*
|
||||
|
@ -1805,6 +1817,7 @@ jsonArrayAggFunction
|
|||
| JSON_ARRAYAGG
|
||||
| JSON_EXISTS
|
||||
| JSON_OBJECT
|
||||
| JSON_OBJECTAGG
|
||||
| JSON_QUERY
|
||||
| JSON_VALUE
|
||||
| KEY
|
||||
|
@ -1894,6 +1907,7 @@ jsonArrayAggFunction
|
|||
| UNBOUNDED
|
||||
| UNCONDITIONAL
|
||||
| UNION
|
||||
| UNIQUE
|
||||
| UPDATE
|
||||
| USING
|
||||
| VALUE
|
||||
|
|
|
@ -469,9 +469,12 @@ public class CockroachDialect extends Dialect {
|
|||
functionFactory.arrayToString_postgresql();
|
||||
|
||||
functionFactory.jsonValue_cockroachdb();
|
||||
functionFactory.jsonExists_postgresql();
|
||||
functionFactory.jsonQuery_cockroachdb();
|
||||
functionFactory.jsonExists_cockroachdb();
|
||||
functionFactory.jsonObject_postgresql();
|
||||
functionFactory.jsonArray_postgresql();
|
||||
functionFactory.jsonArrayAgg_postgresql( false );
|
||||
functionFactory.jsonObjectAgg_postgresql( false );
|
||||
|
||||
// Postgres uses # instead of ^ for XOR
|
||||
functionContributions.getFunctionRegistry().patternDescriptorBuilder( "bitxor", "(?1#?2)" )
|
||||
|
|
|
@ -423,6 +423,7 @@ public class DB2Dialect extends Dialect {
|
|||
functionFactory.jsonObject_db2();
|
||||
functionFactory.jsonArray_db2();
|
||||
functionFactory.jsonArrayAgg_db2();
|
||||
functionFactory.jsonObjectAgg_db2();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -350,6 +350,7 @@ public class H2Dialect extends Dialect {
|
|||
functionFactory.jsonQuery_h2();
|
||||
functionFactory.jsonExists_h2();
|
||||
functionFactory.jsonArrayAgg_h2();
|
||||
functionFactory.jsonObjectAgg_h2();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -499,6 +499,7 @@ public class HANADialect extends Dialect {
|
|||
functionFactory.jsonObject_hana();
|
||||
functionFactory.jsonArray_hana();
|
||||
functionFactory.jsonArrayAgg_hana();
|
||||
functionFactory.jsonObjectAgg_hana();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -209,6 +209,7 @@ public class HSQLDialect extends Dialect {
|
|||
functionFactory.jsonObject_hsqldb();
|
||||
functionFactory.jsonArray_hsqldb();
|
||||
functionFactory.jsonArrayAgg_hsqldb();
|
||||
functionFactory.jsonObjectAgg_h2();
|
||||
}
|
||||
|
||||
//trim() requires parameters to be cast when used as trim character
|
||||
|
|
|
@ -97,6 +97,7 @@ public class MariaDBDialect extends MySQLDialect {
|
|||
commonFunctionFactory.jsonArray_mariadb();
|
||||
commonFunctionFactory.jsonQuery_mariadb();
|
||||
commonFunctionFactory.jsonArrayAgg_mariadb();
|
||||
commonFunctionFactory.jsonObjectAgg_mariadb();
|
||||
commonFunctionFactory.inverseDistributionOrderedSetAggregates_windowEmulation();
|
||||
functionContributions.getFunctionRegistry().patternDescriptorBuilder( "median", "median(?1) over ()" )
|
||||
.setInvariantType( functionContributions.getTypeConfiguration().getBasicTypeRegistry().resolve( StandardBasicTypes.DOUBLE ) )
|
||||
|
|
|
@ -644,6 +644,7 @@ public class MySQLDialect extends Dialect {
|
|||
functionFactory.jsonObject_mysql();
|
||||
functionFactory.jsonArray_mysql();
|
||||
functionFactory.jsonArrayAgg_mysql();
|
||||
functionFactory.jsonObjectAgg_mysql();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -599,7 +599,8 @@ public class PostgreSQLDialect extends Dialect {
|
|||
functionFactory.jsonExists();
|
||||
functionFactory.jsonObject();
|
||||
functionFactory.jsonArray();
|
||||
functionFactory.jsonArrayAgg();
|
||||
functionFactory.jsonArrayAgg_postgresql( true );
|
||||
functionFactory.jsonObjectAgg_postgresql( true );
|
||||
}
|
||||
else {
|
||||
functionFactory.jsonValue_postgresql();
|
||||
|
@ -608,12 +609,14 @@ public class PostgreSQLDialect extends Dialect {
|
|||
if ( getVersion().isSameOrAfter( 16 ) ) {
|
||||
functionFactory.jsonObject();
|
||||
functionFactory.jsonArray();
|
||||
functionFactory.jsonArrayAgg();
|
||||
functionFactory.jsonArrayAgg_postgresql( true );
|
||||
functionFactory.jsonObjectAgg_postgresql( true );
|
||||
}
|
||||
else {
|
||||
functionFactory.jsonObject_postgresql();
|
||||
functionFactory.jsonArray_postgresql();
|
||||
functionFactory.jsonArrayAgg_postgresql();
|
||||
functionFactory.jsonArrayAgg_postgresql( false );
|
||||
functionFactory.jsonObjectAgg_postgresql( false );
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -428,6 +428,7 @@ public class SQLServerDialect extends AbstractTransactSQLDialect {
|
|||
if ( getVersion().isSameOrAfter( 14 ) ) {
|
||||
functionFactory.listagg_stringAggWithinGroup( "varchar(max)" );
|
||||
functionFactory.jsonArrayAgg_sqlserver();
|
||||
functionFactory.jsonObjectAgg_sqlserver();
|
||||
}
|
||||
if ( getVersion().isSameOrAfter( 16 ) ) {
|
||||
functionFactory.leastGreatest();
|
||||
|
|
|
@ -76,17 +76,22 @@ import org.hibernate.dialect.function.array.OracleArrayConstructorFunction;
|
|||
import org.hibernate.dialect.function.array.OracleArrayContainsFunction;
|
||||
import org.hibernate.dialect.function.array.PostgreSQLArrayPositionsFunction;
|
||||
import org.hibernate.dialect.function.array.PostgreSQLArrayTrimEmulation;
|
||||
import org.hibernate.dialect.function.json.CockroachDBJsonExistsFunction;
|
||||
import org.hibernate.dialect.function.json.CockroachDBJsonQueryFunction;
|
||||
import org.hibernate.dialect.function.json.CockroachDBJsonValueFunction;
|
||||
import org.hibernate.dialect.function.json.DB2JsonArrayAggFunction;
|
||||
import org.hibernate.dialect.function.json.DB2JsonArrayFunction;
|
||||
import org.hibernate.dialect.function.json.DB2JsonObjectAggFunction;
|
||||
import org.hibernate.dialect.function.json.DB2JsonObjectFunction;
|
||||
import org.hibernate.dialect.function.json.H2JsonArrayAggFunction;
|
||||
import org.hibernate.dialect.function.json.H2JsonExistsFunction;
|
||||
import org.hibernate.dialect.function.json.H2JsonObjectAggFunction;
|
||||
import org.hibernate.dialect.function.json.H2JsonQueryFunction;
|
||||
import org.hibernate.dialect.function.json.H2JsonValueFunction;
|
||||
import org.hibernate.dialect.function.json.HANAJsonArrayAggFunction;
|
||||
import org.hibernate.dialect.function.json.HANAJsonArrayFunction;
|
||||
import org.hibernate.dialect.function.json.HANAJsonExistsFunction;
|
||||
import org.hibernate.dialect.function.json.HANAJsonObjectAggFunction;
|
||||
import org.hibernate.dialect.function.json.HANAJsonObjectFunction;
|
||||
import org.hibernate.dialect.function.json.HSQLJsonArrayAggFunction;
|
||||
import org.hibernate.dialect.function.json.HSQLJsonArrayFunction;
|
||||
|
@ -94,16 +99,19 @@ import org.hibernate.dialect.function.json.HSQLJsonObjectFunction;
|
|||
import org.hibernate.dialect.function.json.JsonArrayAggFunction;
|
||||
import org.hibernate.dialect.function.json.JsonArrayFunction;
|
||||
import org.hibernate.dialect.function.json.JsonExistsFunction;
|
||||
import org.hibernate.dialect.function.json.JsonObjectAggFunction;
|
||||
import org.hibernate.dialect.function.json.JsonObjectFunction;
|
||||
import org.hibernate.dialect.function.json.JsonQueryFunction;
|
||||
import org.hibernate.dialect.function.json.JsonValueFunction;
|
||||
import org.hibernate.dialect.function.json.MariaDBJsonArrayAggFunction;
|
||||
import org.hibernate.dialect.function.json.MariaDBJsonArrayFunction;
|
||||
import org.hibernate.dialect.function.json.MariaDBJsonObjectAggFunction;
|
||||
import org.hibernate.dialect.function.json.MariaDBJsonQueryFunction;
|
||||
import org.hibernate.dialect.function.json.MariaDBJsonValueFunction;
|
||||
import org.hibernate.dialect.function.json.MySQLJsonArrayAggFunction;
|
||||
import org.hibernate.dialect.function.json.MySQLJsonArrayFunction;
|
||||
import org.hibernate.dialect.function.json.MySQLJsonExistsFunction;
|
||||
import org.hibernate.dialect.function.json.MySQLJsonObjectAggFunction;
|
||||
import org.hibernate.dialect.function.json.MySQLJsonObjectFunction;
|
||||
import org.hibernate.dialect.function.json.MySQLJsonQueryFunction;
|
||||
import org.hibernate.dialect.function.json.MySQLJsonValueFunction;
|
||||
|
@ -113,12 +121,14 @@ import org.hibernate.dialect.function.json.OracleJsonObjectFunction;
|
|||
import org.hibernate.dialect.function.json.PostgreSQLJsonArrayAggFunction;
|
||||
import org.hibernate.dialect.function.json.PostgreSQLJsonArrayFunction;
|
||||
import org.hibernate.dialect.function.json.PostgreSQLJsonExistsFunction;
|
||||
import org.hibernate.dialect.function.json.PostgreSQLJsonObjectAggFunction;
|
||||
import org.hibernate.dialect.function.json.PostgreSQLJsonObjectFunction;
|
||||
import org.hibernate.dialect.function.json.PostgreSQLJsonQueryFunction;
|
||||
import org.hibernate.dialect.function.json.PostgreSQLJsonValueFunction;
|
||||
import org.hibernate.dialect.function.json.SQLServerJsonArrayAggFunction;
|
||||
import org.hibernate.dialect.function.json.SQLServerJsonArrayFunction;
|
||||
import org.hibernate.dialect.function.json.SQLServerJsonExistsFunction;
|
||||
import org.hibernate.dialect.function.json.SQLServerJsonObjectAggFunction;
|
||||
import org.hibernate.dialect.function.json.SQLServerJsonObjectFunction;
|
||||
import org.hibernate.dialect.function.json.SQLServerJsonQueryFunction;
|
||||
import org.hibernate.dialect.function.json.SQLServerJsonValueFunction;
|
||||
|
@ -3458,6 +3468,13 @@ public class CommonFunctionFactory {
|
|||
functionRegistry.register( "json_query", new PostgreSQLJsonQueryFunction( typeConfiguration ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* CockroachDB json_query() function
|
||||
*/
|
||||
public void jsonQuery_cockroachdb() {
|
||||
functionRegistry.register( "json_query", new CockroachDBJsonQueryFunction( typeConfiguration ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* MySQL json_query() function
|
||||
*/
|
||||
|
@ -3528,6 +3545,13 @@ public class CommonFunctionFactory {
|
|||
functionRegistry.register( "json_exists", new PostgreSQLJsonExistsFunction( typeConfiguration ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* CockroachDB json_exists() function
|
||||
*/
|
||||
public void jsonExists_cockroachdb() {
|
||||
functionRegistry.register( "json_exists", new CockroachDBJsonExistsFunction( typeConfiguration ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* MySQL json_exists() function
|
||||
*/
|
||||
|
@ -3661,13 +3685,6 @@ public class CommonFunctionFactory {
|
|||
functionRegistry.register( "json_array", new PostgreSQLJsonArrayFunction( typeConfiguration ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Standard json_arrayagg() function
|
||||
*/
|
||||
public void jsonArrayAgg() {
|
||||
functionRegistry.register( "json_arrayagg", new JsonArrayAggFunction( true, typeConfiguration ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* H2 json_arrayagg() function
|
||||
*/
|
||||
|
@ -3692,8 +3709,8 @@ public class CommonFunctionFactory {
|
|||
/**
|
||||
* PostgreSQL json_arrayagg() function
|
||||
*/
|
||||
public void jsonArrayAgg_postgresql() {
|
||||
functionRegistry.register( "json_arrayagg", new PostgreSQLJsonArrayAggFunction( typeConfiguration ) );
|
||||
public void jsonArrayAgg_postgresql(boolean supportsStandard) {
|
||||
functionRegistry.register( "json_arrayagg", new PostgreSQLJsonArrayAggFunction( supportsStandard, typeConfiguration ) );
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -3730,4 +3747,53 @@ public class CommonFunctionFactory {
|
|||
public void jsonArrayAgg_hana() {
|
||||
functionRegistry.register( "json_arrayagg", new HANAJsonArrayAggFunction( typeConfiguration ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* json_objectagg() function for H2 and HSQLDB
|
||||
*/
|
||||
public void jsonObjectAgg_h2() {
|
||||
functionRegistry.register( "json_objectagg", new H2JsonObjectAggFunction( typeConfiguration ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* PostgreSQL json_objectagg() function
|
||||
*/
|
||||
public void jsonObjectAgg_postgresql(boolean supportsStandard) {
|
||||
functionRegistry.register( "json_objectagg", new PostgreSQLJsonObjectAggFunction( supportsStandard, typeConfiguration ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* MySQL json_objectagg() function
|
||||
*/
|
||||
public void jsonObjectAgg_mysql() {
|
||||
functionRegistry.register( "json_objectagg", new MySQLJsonObjectAggFunction( typeConfiguration ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* MariaDB json_objectagg() function
|
||||
*/
|
||||
public void jsonObjectAgg_mariadb() {
|
||||
functionRegistry.register( "json_objectagg", new MariaDBJsonObjectAggFunction( typeConfiguration ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* SQL Server json_objectagg() function
|
||||
*/
|
||||
public void jsonObjectAgg_sqlserver() {
|
||||
functionRegistry.register( "json_objectagg", new SQLServerJsonObjectAggFunction( typeConfiguration ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* HANA json_objectagg() function
|
||||
*/
|
||||
public void jsonObjectAgg_hana() {
|
||||
functionRegistry.register( "json_objectagg", new HANAJsonObjectAggFunction( typeConfiguration ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* DB2 json_objectagg() function
|
||||
*/
|
||||
public void jsonObjectAgg_db2() {
|
||||
functionRegistry.register( "json_objectagg", new DB2JsonObjectAggFunction( typeConfiguration ) );
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 java.util.List;
|
||||
|
||||
import org.hibernate.QueryException;
|
||||
import org.hibernate.dialect.Dialect;
|
||||
import org.hibernate.query.ReturnableType;
|
||||
import org.hibernate.sql.ast.SqlAstTranslator;
|
||||
import org.hibernate.sql.ast.spi.SqlAppender;
|
||||
import org.hibernate.sql.ast.tree.expression.Expression;
|
||||
import org.hibernate.sql.ast.tree.expression.JdbcParameter;
|
||||
import org.hibernate.sql.ast.tree.expression.JsonPathPassingClause;
|
||||
import org.hibernate.type.spi.TypeConfiguration;
|
||||
|
||||
/**
|
||||
* CockroachDB json_exists function.
|
||||
*/
|
||||
public class CockroachDBJsonExistsFunction extends JsonExistsFunction {
|
||||
|
||||
public CockroachDBJsonExistsFunction(TypeConfiguration typeConfiguration) {
|
||||
super( typeConfiguration, true, true );
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void render(
|
||||
SqlAppender sqlAppender,
|
||||
JsonExistsArguments arguments,
|
||||
ReturnableType<?> returnType,
|
||||
SqlAstTranslator<?> walker) {
|
||||
final String jsonPath;
|
||||
try {
|
||||
jsonPath = walker.getLiteralValue( arguments.jsonPath() );
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new QueryException( "CockroachDB json_value only support literal json paths, but got " + arguments.jsonPath() );
|
||||
}
|
||||
final List<JsonPathHelper.JsonPathElement> jsonPathElements = JsonPathHelper.parseJsonPathElements( jsonPath );
|
||||
final boolean needsCast = !arguments.isJsonType() && arguments.jsonDocument() instanceof JdbcParameter;
|
||||
if ( needsCast ) {
|
||||
sqlAppender.appendSql( "cast(" );
|
||||
}
|
||||
else {
|
||||
sqlAppender.appendSql( '(' );
|
||||
}
|
||||
arguments.jsonDocument().accept( walker );
|
||||
if ( needsCast ) {
|
||||
sqlAppender.appendSql( " as jsonb)" );
|
||||
}
|
||||
else {
|
||||
sqlAppender.appendSql( ')' );
|
||||
}
|
||||
sqlAppender.appendSql( "#>>array" );
|
||||
char separator = '[';
|
||||
final Dialect dialect = walker.getSessionFactory().getJdbcServices().getDialect();
|
||||
for ( JsonPathHelper.JsonPathElement jsonPathElement : jsonPathElements ) {
|
||||
sqlAppender.appendSql( separator );
|
||||
if ( jsonPathElement instanceof JsonPathHelper.JsonAttribute attribute ) {
|
||||
dialect.appendLiteral( sqlAppender, attribute.attribute() );
|
||||
}
|
||||
else 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() );
|
||||
sqlAppender.appendSql( '\'' );
|
||||
}
|
||||
separator = ',';
|
||||
}
|
||||
sqlAppender.appendSql( "] is not null" );
|
||||
}
|
||||
}
|
|
@ -0,0 +1,107 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
|
||||
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
|
||||
*/
|
||||
package org.hibernate.dialect.function.json;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.hibernate.QueryException;
|
||||
import org.hibernate.dialect.Dialect;
|
||||
import org.hibernate.query.ReturnableType;
|
||||
import org.hibernate.sql.ast.SqlAstTranslator;
|
||||
import org.hibernate.sql.ast.spi.SqlAppender;
|
||||
import org.hibernate.sql.ast.tree.expression.Expression;
|
||||
import org.hibernate.sql.ast.tree.expression.JdbcParameter;
|
||||
import org.hibernate.sql.ast.tree.expression.JsonPathPassingClause;
|
||||
import org.hibernate.sql.ast.tree.expression.JsonQueryEmptyBehavior;
|
||||
import org.hibernate.sql.ast.tree.expression.JsonQueryErrorBehavior;
|
||||
import org.hibernate.sql.ast.tree.expression.JsonQueryWrapMode;
|
||||
import org.hibernate.type.spi.TypeConfiguration;
|
||||
|
||||
/**
|
||||
* CockroachDB json_query function.
|
||||
*/
|
||||
public class CockroachDBJsonQueryFunction extends JsonQueryFunction {
|
||||
|
||||
public CockroachDBJsonQueryFunction(TypeConfiguration typeConfiguration) {
|
||||
super( typeConfiguration, true, true );
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void render(
|
||||
SqlAppender sqlAppender,
|
||||
JsonQueryArguments arguments,
|
||||
ReturnableType<?> returnType,
|
||||
SqlAstTranslator<?> walker) {
|
||||
// jsonb_path_query functions error by default
|
||||
if ( arguments.errorBehavior() != null && arguments.errorBehavior() != JsonQueryErrorBehavior.ERROR ) {
|
||||
throw new QueryException( "Can't emulate on error clause on PostgreSQL" );
|
||||
}
|
||||
if ( arguments.emptyBehavior() != null && arguments.emptyBehavior() != JsonQueryEmptyBehavior.NULL ) {
|
||||
throw new QueryException( "Can't emulate on empty clause on PostgreSQL" );
|
||||
}
|
||||
final JsonQueryWrapMode wrapMode = arguments.wrapMode();
|
||||
|
||||
if ( wrapMode == JsonQueryWrapMode.WITH_WRAPPER ) {
|
||||
sqlAppender.appendSql( "jsonb_build_array(" );
|
||||
}
|
||||
final String jsonPath;
|
||||
try {
|
||||
jsonPath = walker.getLiteralValue( arguments.jsonPath() );
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new QueryException( "CockroachDB json_value only support literal json paths, but got " + arguments.jsonPath() );
|
||||
}
|
||||
final List<JsonPathHelper.JsonPathElement> jsonPathElements = JsonPathHelper.parseJsonPathElements( jsonPath );
|
||||
final boolean needsCast = !arguments.isJsonType() && arguments.jsonDocument() instanceof JdbcParameter;
|
||||
if ( needsCast ) {
|
||||
sqlAppender.appendSql( "cast(" );
|
||||
}
|
||||
else {
|
||||
sqlAppender.appendSql( '(' );
|
||||
}
|
||||
arguments.jsonDocument().accept( walker );
|
||||
if ( needsCast ) {
|
||||
sqlAppender.appendSql( " as jsonb)" );
|
||||
}
|
||||
else {
|
||||
sqlAppender.appendSql( ')' );
|
||||
}
|
||||
sqlAppender.appendSql( "#>array" );
|
||||
char separator = '[';
|
||||
final Dialect dialect = walker.getSessionFactory().getJdbcServices().getDialect();
|
||||
for ( JsonPathHelper.JsonPathElement jsonPathElement : jsonPathElements ) {
|
||||
sqlAppender.appendSql( separator );
|
||||
if ( jsonPathElement instanceof JsonPathHelper.JsonAttribute attribute ) {
|
||||
dialect.appendLiteral( sqlAppender, attribute.attribute() );
|
||||
}
|
||||
else 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() );
|
||||
sqlAppender.appendSql( '\'' );
|
||||
}
|
||||
separator = ',';
|
||||
}
|
||||
sqlAppender.appendSql( ']' );
|
||||
|
||||
if ( wrapMode == JsonQueryWrapMode.WITH_WRAPPER ) {
|
||||
sqlAppender.appendSql( ")" );
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,91 @@
|
|||
/*
|
||||
* 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.metamodel.mapping.JdbcMappingContainer;
|
||||
import org.hibernate.query.ReturnableType;
|
||||
import org.hibernate.sql.ast.Clause;
|
||||
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.JsonNullBehavior;
|
||||
import org.hibernate.sql.ast.tree.expression.JsonObjectAggUniqueKeysBehavior;
|
||||
import org.hibernate.sql.ast.tree.predicate.Predicate;
|
||||
import org.hibernate.type.descriptor.jdbc.JdbcType;
|
||||
import org.hibernate.type.spi.TypeConfiguration;
|
||||
|
||||
/**
|
||||
* DB2 json_objectagg function.
|
||||
*/
|
||||
public class DB2JsonObjectAggFunction extends JsonObjectAggFunction {
|
||||
|
||||
public DB2JsonObjectAggFunction(TypeConfiguration typeConfiguration) {
|
||||
super( ",", false, typeConfiguration );
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void render(
|
||||
SqlAppender sqlAppender,
|
||||
JsonObjectAggArguments arguments,
|
||||
Predicate filter,
|
||||
ReturnableType<?> returnType,
|
||||
SqlAstTranslator<?> translator) {
|
||||
final boolean caseWrapper = filter != null;
|
||||
if ( arguments.uniqueKeysBehavior() == JsonObjectAggUniqueKeysBehavior.WITH ) {
|
||||
throw new QueryException( "Can't emulate json_objectagg 'with unique keys' clause." );
|
||||
}
|
||||
sqlAppender.appendSql( "'{'||listagg(" );
|
||||
renderArgument( sqlAppender, arguments.key(), arguments.nullBehavior(), translator );
|
||||
sqlAppender.appendSql( "||':'||" );
|
||||
if ( caseWrapper ) {
|
||||
if ( arguments.nullBehavior() != JsonNullBehavior.ABSENT ) {
|
||||
throw new QueryException( "Can't emulate json_objectagg filter clause when using 'null on null' clause." );
|
||||
}
|
||||
translator.getCurrentClauseStack().push( Clause.WHERE );
|
||||
sqlAppender.appendSql( "case when " );
|
||||
filter.accept( translator );
|
||||
translator.getCurrentClauseStack().pop();
|
||||
sqlAppender.appendSql( " then " );
|
||||
renderArgument( sqlAppender, arguments.value(), arguments.nullBehavior(), translator );
|
||||
sqlAppender.appendSql( " else null end)" );
|
||||
}
|
||||
else {
|
||||
renderArgument( sqlAppender, arguments.value(), arguments.nullBehavior(), translator );
|
||||
}
|
||||
sqlAppender.appendSql( ",',')||'}'" );
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void renderArgument(
|
||||
SqlAppender sqlAppender,
|
||||
Expression arg,
|
||||
JsonNullBehavior nullBehavior,
|
||||
SqlAstTranslator<?> translator) {
|
||||
// Convert SQL type to JSON type
|
||||
if ( nullBehavior == JsonNullBehavior.NULL ) {
|
||||
sqlAppender.appendSql( "coalesce(" );
|
||||
}
|
||||
final JdbcMappingContainer expressionType = arg.getExpressionType();
|
||||
if ( expressionType != null && expressionType.getSingleJdbcMapping().getJdbcType().isJson() ) {
|
||||
arg.accept( translator );
|
||||
}
|
||||
else if ( expressionType != null && expressionType.getSingleJdbcMapping().getJdbcType().isBinary() ) {
|
||||
sqlAppender.appendSql( "json_query(json_array(rawtohex(" );
|
||||
arg.accept( translator );
|
||||
sqlAppender.appendSql( ") null on null),'$.*')" );
|
||||
}
|
||||
else {
|
||||
sqlAppender.appendSql( "json_query(json_array(" );
|
||||
arg.accept( translator );
|
||||
sqlAppender.appendSql( " null on null),'$.*')" );
|
||||
}
|
||||
if ( nullBehavior == JsonNullBehavior.NULL ) {
|
||||
sqlAppender.appendSql( ",'null')" );
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* 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.sql.ast.SqlAstTranslator;
|
||||
import org.hibernate.sql.ast.spi.SqlAppender;
|
||||
import org.hibernate.type.spi.TypeConfiguration;
|
||||
|
||||
/**
|
||||
* Standard json_objectagg function that uses no returning clause.
|
||||
*/
|
||||
public class H2JsonObjectAggFunction extends JsonObjectAggFunction {
|
||||
|
||||
public H2JsonObjectAggFunction(TypeConfiguration typeConfiguration) {
|
||||
super( ":", true, typeConfiguration );
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void renderReturningClause(
|
||||
SqlAppender sqlAppender,
|
||||
JsonObjectAggArguments arguments,
|
||||
SqlAstTranslator<?> translator) {
|
||||
// No-op
|
||||
}
|
||||
}
|
|
@ -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.metamodel.mapping.JdbcMappingContainer;
|
||||
import org.hibernate.query.ReturnableType;
|
||||
import org.hibernate.sql.ast.Clause;
|
||||
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.JsonNullBehavior;
|
||||
import org.hibernate.sql.ast.tree.expression.JsonObjectAggUniqueKeysBehavior;
|
||||
import org.hibernate.sql.ast.tree.predicate.Predicate;
|
||||
import org.hibernate.type.spi.TypeConfiguration;
|
||||
|
||||
/**
|
||||
* HANA json_objectagg function.
|
||||
*/
|
||||
public class HANAJsonObjectAggFunction extends JsonObjectAggFunction {
|
||||
|
||||
public HANAJsonObjectAggFunction(TypeConfiguration typeConfiguration) {
|
||||
super( ",", false, typeConfiguration );
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void render(
|
||||
SqlAppender sqlAppender,
|
||||
JsonObjectAggArguments arguments,
|
||||
Predicate filter,
|
||||
ReturnableType<?> returnType,
|
||||
SqlAstTranslator<?> translator) {
|
||||
final boolean caseWrapper = filter != null;
|
||||
if ( arguments.uniqueKeysBehavior() == JsonObjectAggUniqueKeysBehavior.WITH ) {
|
||||
throw new QueryException( "Can't emulate json_objectagg 'with unique keys' clause." );
|
||||
}
|
||||
sqlAppender.appendSql( "'{'||string_agg(" );
|
||||
renderArgument( sqlAppender, arguments.key(), arguments.nullBehavior(), translator );
|
||||
sqlAppender.appendSql( "||':'||" );
|
||||
if ( caseWrapper ) {
|
||||
if ( arguments.nullBehavior() != JsonNullBehavior.ABSENT ) {
|
||||
throw new QueryException( "Can't emulate json_objectagg filter clause when using 'null on null' clause." );
|
||||
}
|
||||
translator.getCurrentClauseStack().push( Clause.WHERE );
|
||||
sqlAppender.appendSql( "case when " );
|
||||
filter.accept( translator );
|
||||
translator.getCurrentClauseStack().pop();
|
||||
sqlAppender.appendSql( " then " );
|
||||
renderArgument( sqlAppender, arguments.value(), arguments.nullBehavior(), translator );
|
||||
sqlAppender.appendSql( " else null end)" );
|
||||
}
|
||||
else {
|
||||
renderArgument( sqlAppender, arguments.value(), arguments.nullBehavior(), translator );
|
||||
}
|
||||
sqlAppender.appendSql( ",',')||'}'" );
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void renderArgument(
|
||||
SqlAppender sqlAppender,
|
||||
Expression arg,
|
||||
JsonNullBehavior nullBehavior,
|
||||
SqlAstTranslator<?> translator) {
|
||||
// Convert SQL type to JSON type
|
||||
final JdbcMappingContainer expressionType = arg.getExpressionType();
|
||||
if ( expressionType != null && expressionType.getSingleJdbcMapping().getJdbcType().isJson() ) {
|
||||
sqlAppender.appendSql( "cast(" );
|
||||
arg.accept( translator );
|
||||
sqlAppender.appendSql( " as nvarchar(" + Integer.MAX_VALUE + "))" );
|
||||
}
|
||||
else {
|
||||
if ( nullBehavior != JsonNullBehavior.NULL ) {
|
||||
sqlAppender.appendSql( "nullif(" );
|
||||
}
|
||||
sqlAppender.appendSql( "json_query((select " );
|
||||
arg.accept( translator );
|
||||
sqlAppender.appendSql(
|
||||
" V from sys.dummy for json('arraywrap'='no','omitnull'='no') returns nvarchar(" + Integer.MAX_VALUE + ")),'$.V')" );
|
||||
if ( nullBehavior != JsonNullBehavior.NULL ) {
|
||||
sqlAppender.appendSql( ",'null')" );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,172 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
|
||||
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
|
||||
*/
|
||||
package org.hibernate.dialect.function.json;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.hibernate.QueryException;
|
||||
import org.hibernate.query.ReturnableType;
|
||||
import org.hibernate.query.sqm.function.AbstractSqmSelfRenderingFunctionDescriptor;
|
||||
import org.hibernate.query.sqm.function.FunctionKind;
|
||||
import org.hibernate.query.sqm.produce.function.StandardArgumentsValidators;
|
||||
import org.hibernate.query.sqm.produce.function.StandardFunctionReturnTypeResolvers;
|
||||
import org.hibernate.sql.ast.Clause;
|
||||
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.JsonNullBehavior;
|
||||
import org.hibernate.sql.ast.tree.expression.JsonObjectAggUniqueKeysBehavior;
|
||||
import org.hibernate.sql.ast.tree.predicate.Predicate;
|
||||
import org.hibernate.type.SqlTypes;
|
||||
import org.hibernate.type.spi.TypeConfiguration;
|
||||
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
/**
|
||||
* Standard json_objectagg function.
|
||||
*/
|
||||
public class JsonObjectAggFunction extends AbstractSqmSelfRenderingFunctionDescriptor {
|
||||
|
||||
protected final String valueSeparator;
|
||||
protected final boolean supportsFilter;
|
||||
|
||||
public JsonObjectAggFunction(String valueSeparator, boolean supportsFilter, TypeConfiguration typeConfiguration) {
|
||||
super(
|
||||
"json_objectagg",
|
||||
FunctionKind.AGGREGATE,
|
||||
StandardArgumentsValidators.between( 2, 4 ),
|
||||
StandardFunctionReturnTypeResolvers.invariant(
|
||||
typeConfiguration.getBasicTypeRegistry().resolve( String.class, SqlTypes.JSON )
|
||||
),
|
||||
null
|
||||
);
|
||||
this.supportsFilter = supportsFilter;
|
||||
this.valueSeparator = valueSeparator;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(
|
||||
SqlAppender sqlAppender,
|
||||
List<? extends SqlAstNode> sqlAstArguments,
|
||||
ReturnableType<?> returnType,
|
||||
SqlAstTranslator<?> walker) {
|
||||
render( sqlAppender, sqlAstArguments, null, returnType, walker );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(
|
||||
SqlAppender sqlAppender,
|
||||
List<? extends SqlAstNode> sqlAstArguments,
|
||||
Predicate filter,
|
||||
ReturnableType<?> returnType,
|
||||
SqlAstTranslator<?> translator) {
|
||||
render( sqlAppender, JsonObjectAggArguments.extract( sqlAstArguments ), filter, returnType, translator );
|
||||
}
|
||||
|
||||
protected void render(
|
||||
SqlAppender sqlAppender,
|
||||
JsonObjectAggArguments arguments,
|
||||
Predicate filter,
|
||||
ReturnableType<?> returnType,
|
||||
SqlAstTranslator<?> translator) {
|
||||
final boolean caseWrapper = filter != null && !translator.supportsFilterClause();
|
||||
sqlAppender.appendSql( "json_objectagg(" );
|
||||
arguments.key().accept( translator );
|
||||
sqlAppender.appendSql( valueSeparator );
|
||||
if ( caseWrapper ) {
|
||||
if ( arguments.nullBehavior() != JsonNullBehavior.ABSENT ) {
|
||||
throw new QueryException( "Can't emulate json_objectagg filter clause when using 'null on null' clause." );
|
||||
}
|
||||
translator.getCurrentClauseStack().push( Clause.WHERE );
|
||||
sqlAppender.appendSql( "case when " );
|
||||
filter.accept( translator );
|
||||
translator.getCurrentClauseStack().pop();
|
||||
sqlAppender.appendSql( " then " );
|
||||
renderArgument( sqlAppender, arguments.value(), arguments.nullBehavior(), translator );
|
||||
sqlAppender.appendSql( " else null end)" );
|
||||
}
|
||||
else {
|
||||
renderArgument( sqlAppender, arguments.value(), arguments.nullBehavior(), translator );
|
||||
}
|
||||
if ( arguments.nullBehavior() == JsonNullBehavior.NULL ) {
|
||||
sqlAppender.appendSql( " null on null" );
|
||||
}
|
||||
else {
|
||||
sqlAppender.appendSql( " absent on null" );
|
||||
}
|
||||
renderUniqueAndReturningClause( sqlAppender, arguments, translator );
|
||||
sqlAppender.appendSql( ')' );
|
||||
|
||||
if ( !caseWrapper && filter != null ) {
|
||||
translator.getCurrentClauseStack().push( Clause.WHERE );
|
||||
sqlAppender.appendSql( " filter (where " );
|
||||
filter.accept( translator );
|
||||
sqlAppender.appendSql( ')' );
|
||||
translator.getCurrentClauseStack().pop();
|
||||
}
|
||||
}
|
||||
|
||||
protected void renderArgument(
|
||||
SqlAppender sqlAppender,
|
||||
Expression arg,
|
||||
JsonNullBehavior nullBehavior,
|
||||
SqlAstTranslator<?> translator) {
|
||||
arg.accept( translator );
|
||||
}
|
||||
|
||||
protected void renderUniqueAndReturningClause(SqlAppender sqlAppender, JsonObjectAggArguments arguments, SqlAstTranslator<?> translator) {
|
||||
renderReturningClause( sqlAppender, arguments, translator );
|
||||
renderUniqueClause( sqlAppender, arguments, translator );
|
||||
}
|
||||
|
||||
protected void renderReturningClause(SqlAppender sqlAppender, JsonObjectAggArguments arguments, SqlAstTranslator<?> translator) {
|
||||
sqlAppender.appendSql( " returning " );
|
||||
sqlAppender.appendSql(
|
||||
translator.getSessionFactory().getTypeConfiguration().getDdlTypeRegistry()
|
||||
.getTypeName( SqlTypes.JSON, translator.getSessionFactory().getJdbcServices().getDialect() )
|
||||
);
|
||||
}
|
||||
|
||||
protected void renderUniqueClause(SqlAppender sqlAppender, JsonObjectAggArguments arguments, SqlAstTranslator<?> translator) {
|
||||
if ( arguments.uniqueKeysBehavior() == JsonObjectAggUniqueKeysBehavior.WITH ) {
|
||||
sqlAppender.appendSql( " with unique keys" );
|
||||
}
|
||||
}
|
||||
|
||||
protected record JsonObjectAggArguments(
|
||||
Expression key,
|
||||
Expression value,
|
||||
@Nullable JsonNullBehavior nullBehavior,
|
||||
@Nullable JsonObjectAggUniqueKeysBehavior uniqueKeysBehavior) {
|
||||
public static JsonObjectAggArguments extract(List<? extends SqlAstNode> sqlAstArguments) {
|
||||
int nextIndex = 2;
|
||||
JsonNullBehavior nullBehavior = null;
|
||||
JsonObjectAggUniqueKeysBehavior uniqueKeysBehavior = null;
|
||||
if ( nextIndex < sqlAstArguments.size() ) {
|
||||
final SqlAstNode node = sqlAstArguments.get( nextIndex );
|
||||
if ( node instanceof JsonNullBehavior ) {
|
||||
nullBehavior = (JsonNullBehavior) node;
|
||||
nextIndex++;
|
||||
}
|
||||
}
|
||||
if ( nextIndex < sqlAstArguments.size() ) {
|
||||
final SqlAstNode node = sqlAstArguments.get( nextIndex );
|
||||
if ( node instanceof JsonObjectAggUniqueKeysBehavior ) {
|
||||
uniqueKeysBehavior = (JsonObjectAggUniqueKeysBehavior) node;
|
||||
nextIndex++;
|
||||
}
|
||||
}
|
||||
return new JsonObjectAggArguments(
|
||||
(Expression) sqlAstArguments.get( 0 ),
|
||||
(Expression) sqlAstArguments.get( 1 ),
|
||||
nullBehavior,
|
||||
uniqueKeysBehavior
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -91,8 +91,14 @@ public class MariaDBJsonArrayAggFunction extends JsonArrayAggFunction {
|
|||
JsonNullBehavior nullBehavior,
|
||||
SqlAstTranslator<?> translator) {
|
||||
// Convert SQL type to JSON type
|
||||
if ( nullBehavior != JsonNullBehavior.NULL ) {
|
||||
sqlAppender.appendSql( "nullif(" );
|
||||
}
|
||||
sqlAppender.appendSql( "json_extract(json_array(" );
|
||||
arg.accept( translator );
|
||||
sqlAppender.appendSql( "),'$[0]')" );
|
||||
if ( nullBehavior != JsonNullBehavior.NULL ) {
|
||||
sqlAppender.appendSql( ",'null')" );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* 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.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.JsonNullBehavior;
|
||||
import org.hibernate.type.spi.TypeConfiguration;
|
||||
|
||||
/**
|
||||
* MariaDB json_objectagg function.
|
||||
*/
|
||||
public class MariaDBJsonObjectAggFunction extends MySQLJsonObjectAggFunction {
|
||||
|
||||
public MariaDBJsonObjectAggFunction(TypeConfiguration typeConfiguration) {
|
||||
super( typeConfiguration );
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void renderArgument(
|
||||
SqlAppender sqlAppender,
|
||||
Expression arg,
|
||||
JsonNullBehavior nullBehavior,
|
||||
SqlAstTranslator<?> translator) {
|
||||
// Convert SQL type to JSON type
|
||||
if ( nullBehavior != JsonNullBehavior.NULL ) {
|
||||
sqlAppender.appendSql( "nullif(" );
|
||||
}
|
||||
sqlAppender.appendSql( "json_extract(json_array(" );
|
||||
arg.accept( translator );
|
||||
sqlAppender.appendSql( "),'$[0]')" );
|
||||
if ( nullBehavior != JsonNullBehavior.NULL ) {
|
||||
sqlAppender.appendSql( ",'null')" );
|
||||
}
|
||||
}
|
||||
}
|
|
@ -91,8 +91,14 @@ public class MySQLJsonArrayAggFunction extends JsonArrayAggFunction {
|
|||
JsonNullBehavior nullBehavior,
|
||||
SqlAstTranslator<?> translator) {
|
||||
// Convert SQL type to JSON type
|
||||
if ( nullBehavior != JsonNullBehavior.NULL ) {
|
||||
sqlAppender.appendSql( "nullif(" );
|
||||
}
|
||||
sqlAppender.appendSql( "json_extract(json_array(" );
|
||||
arg.accept( translator );
|
||||
sqlAppender.appendSql( "),'$[0]')" );
|
||||
if ( nullBehavior != JsonNullBehavior.NULL ) {
|
||||
sqlAppender.appendSql( ",cast('null' as json))" );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,81 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
|
||||
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
|
||||
*/
|
||||
package org.hibernate.dialect.function.json;
|
||||
|
||||
import org.hibernate.QueryException;
|
||||
import org.hibernate.query.ReturnableType;
|
||||
import org.hibernate.sql.ast.Clause;
|
||||
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.Distinct;
|
||||
import org.hibernate.sql.ast.tree.expression.Expression;
|
||||
import org.hibernate.sql.ast.tree.expression.JsonNullBehavior;
|
||||
import org.hibernate.sql.ast.tree.expression.JsonObjectAggUniqueKeysBehavior;
|
||||
import org.hibernate.sql.ast.tree.predicate.Predicate;
|
||||
import org.hibernate.type.SqlTypes;
|
||||
import org.hibernate.type.spi.TypeConfiguration;
|
||||
|
||||
/**
|
||||
* MySQL json_objectagg function.
|
||||
*/
|
||||
public class MySQLJsonObjectAggFunction extends JsonObjectAggFunction {
|
||||
|
||||
public MySQLJsonObjectAggFunction(TypeConfiguration typeConfiguration) {
|
||||
super( ",", false, typeConfiguration );
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void render(
|
||||
SqlAppender sqlAppender,
|
||||
JsonObjectAggArguments arguments,
|
||||
Predicate filter,
|
||||
ReturnableType<?> returnType,
|
||||
SqlAstTranslator<?> translator) {
|
||||
final boolean caseWrapper = filter != null;
|
||||
if ( arguments.uniqueKeysBehavior() == JsonObjectAggUniqueKeysBehavior.WITH ) {
|
||||
throw new QueryException( "Can't emulate json_objectagg 'with unique keys' clause." );
|
||||
}
|
||||
sqlAppender.appendSql( "concat('{',group_concat(concat(json_quote(" );
|
||||
arguments.key().accept( translator );
|
||||
sqlAppender.appendSql( "),':'," );
|
||||
if ( caseWrapper ) {
|
||||
if ( arguments.nullBehavior() != JsonNullBehavior.ABSENT ) {
|
||||
throw new QueryException( "Can't emulate json_objectagg filter clause when using 'null on null' clause." );
|
||||
}
|
||||
translator.getCurrentClauseStack().push( Clause.WHERE );
|
||||
sqlAppender.appendSql( "case when " );
|
||||
filter.accept( translator );
|
||||
translator.getCurrentClauseStack().pop();
|
||||
sqlAppender.appendSql( " then " );
|
||||
renderArgument( sqlAppender, arguments.value(), arguments.nullBehavior(), translator );
|
||||
sqlAppender.appendSql( " else null end)" );
|
||||
}
|
||||
else {
|
||||
renderArgument( sqlAppender, arguments.value(), arguments.nullBehavior(), translator );
|
||||
}
|
||||
sqlAppender.appendSql( ") separator ','),'}')" );
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void renderArgument(
|
||||
SqlAppender sqlAppender,
|
||||
Expression arg,
|
||||
JsonNullBehavior nullBehavior,
|
||||
SqlAstTranslator<?> translator) {
|
||||
// Convert SQL type to JSON type
|
||||
if ( nullBehavior != JsonNullBehavior.NULL ) {
|
||||
sqlAppender.appendSql( "nullif(" );
|
||||
}
|
||||
sqlAppender.appendSql( "json_extract(json_array(" );
|
||||
arg.accept( translator );
|
||||
sqlAppender.appendSql( "),'$[0]')" );
|
||||
if ( nullBehavior != JsonNullBehavior.NULL ) {
|
||||
sqlAppender.appendSql( ",cast('null' as json))" );
|
||||
}
|
||||
}
|
||||
}
|
|
@ -27,8 +27,11 @@ import org.hibernate.type.spi.TypeConfiguration;
|
|||
*/
|
||||
public class PostgreSQLJsonArrayAggFunction extends JsonArrayAggFunction {
|
||||
|
||||
public PostgreSQLJsonArrayAggFunction(TypeConfiguration typeConfiguration) {
|
||||
private final boolean supportsStandard;
|
||||
|
||||
public PostgreSQLJsonArrayAggFunction(boolean supportsStandard, TypeConfiguration typeConfiguration) {
|
||||
super( true, typeConfiguration );
|
||||
this.supportsStandard = supportsStandard;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -39,64 +42,61 @@ public class PostgreSQLJsonArrayAggFunction extends JsonArrayAggFunction {
|
|||
List<SortSpecification> withinGroup,
|
||||
ReturnableType<?> returnType,
|
||||
SqlAstTranslator<?> translator) {
|
||||
final boolean caseWrapper = filter != null && !supportsFilter;
|
||||
final String jsonTypeName = translator.getSessionFactory().getTypeConfiguration().getDdlTypeRegistry()
|
||||
.getTypeName( SqlTypes.JSON, translator.getSessionFactory().getJdbcServices().getDialect() );
|
||||
sqlAppender.appendSql( jsonTypeName );
|
||||
sqlAppender.appendSql( "_agg" );
|
||||
final JsonNullBehavior nullBehavior;
|
||||
if ( sqlAstArguments.size() > 1 ) {
|
||||
nullBehavior = (JsonNullBehavior) sqlAstArguments.get( 1 );
|
||||
if ( supportsStandard ) {
|
||||
super.render( sqlAppender, sqlAstArguments, filter, withinGroup, returnType, translator );
|
||||
}
|
||||
else {
|
||||
nullBehavior = JsonNullBehavior.ABSENT;
|
||||
}
|
||||
if ( nullBehavior != JsonNullBehavior.NULL ) {
|
||||
sqlAppender.appendSql( "_strict" );
|
||||
}
|
||||
sqlAppender.appendSql( '(' );
|
||||
final SqlAstNode firstArg = sqlAstArguments.get( 0 );
|
||||
final Expression arg;
|
||||
if ( firstArg instanceof Distinct ) {
|
||||
sqlAppender.appendSql( "distinct " );
|
||||
arg = ( (Distinct) firstArg ).getExpression();
|
||||
}
|
||||
else {
|
||||
arg = (Expression) firstArg;
|
||||
}
|
||||
if ( caseWrapper ) {
|
||||
if ( nullBehavior != JsonNullBehavior.ABSENT ) {
|
||||
throw new QueryException( "Can't emulate json_arrayagg filter clause when using 'null on null' clause." );
|
||||
final String jsonTypeName = translator.getSessionFactory().getTypeConfiguration().getDdlTypeRegistry()
|
||||
.getTypeName( SqlTypes.JSON, translator.getSessionFactory().getJdbcServices().getDialect() );
|
||||
sqlAppender.appendSql( jsonTypeName );
|
||||
sqlAppender.appendSql( "_agg" );
|
||||
final JsonNullBehavior nullBehavior;
|
||||
if ( sqlAstArguments.size() > 1 ) {
|
||||
nullBehavior = (JsonNullBehavior) sqlAstArguments.get( 1 );
|
||||
}
|
||||
translator.getCurrentClauseStack().push( Clause.WHERE );
|
||||
sqlAppender.appendSql( "case when " );
|
||||
filter.accept( translator );
|
||||
translator.getCurrentClauseStack().pop();
|
||||
sqlAppender.appendSql( " then " );
|
||||
renderArgument( sqlAppender, arg, nullBehavior, translator );
|
||||
sqlAppender.appendSql( " else null end)" );
|
||||
}
|
||||
else {
|
||||
renderArgument( sqlAppender, arg, nullBehavior, translator );
|
||||
}
|
||||
if ( withinGroup != null && !withinGroup.isEmpty() ) {
|
||||
translator.getCurrentClauseStack().push( Clause.WITHIN_GROUP );
|
||||
sqlAppender.appendSql( " order by " );
|
||||
withinGroup.get( 0 ).accept( translator );
|
||||
for ( int i = 1; i < withinGroup.size(); i++ ) {
|
||||
sqlAppender.appendSql( ',' );
|
||||
withinGroup.get( i ).accept( translator );
|
||||
else {
|
||||
nullBehavior = JsonNullBehavior.ABSENT;
|
||||
}
|
||||
sqlAppender.appendSql( '(' );
|
||||
final SqlAstNode firstArg = sqlAstArguments.get( 0 );
|
||||
final Expression arg;
|
||||
if ( firstArg instanceof Distinct ) {
|
||||
sqlAppender.appendSql( "distinct " );
|
||||
arg = ( (Distinct) firstArg ).getExpression();
|
||||
}
|
||||
else {
|
||||
arg = (Expression) firstArg;
|
||||
}
|
||||
renderArgument( sqlAppender, arg, nullBehavior, translator );
|
||||
if ( withinGroup != null && !withinGroup.isEmpty() ) {
|
||||
translator.getCurrentClauseStack().push( Clause.WITHIN_GROUP );
|
||||
sqlAppender.appendSql( " order by " );
|
||||
withinGroup.get( 0 ).accept( translator );
|
||||
for ( int i = 1; i < withinGroup.size(); i++ ) {
|
||||
sqlAppender.appendSql( ',' );
|
||||
withinGroup.get( i ).accept( translator );
|
||||
}
|
||||
translator.getCurrentClauseStack().pop();
|
||||
}
|
||||
translator.getCurrentClauseStack().pop();
|
||||
}
|
||||
sqlAppender.appendSql( ')' );
|
||||
|
||||
if ( !caseWrapper && filter != null ) {
|
||||
translator.getCurrentClauseStack().push( Clause.WHERE );
|
||||
sqlAppender.appendSql( " filter (where " );
|
||||
filter.accept( translator );
|
||||
sqlAppender.appendSql( ')' );
|
||||
translator.getCurrentClauseStack().pop();
|
||||
|
||||
if ( filter != null ) {
|
||||
translator.getCurrentClauseStack().push( Clause.WHERE );
|
||||
sqlAppender.appendSql( " filter (where " );
|
||||
filter.accept( translator );
|
||||
if ( nullBehavior != JsonNullBehavior.NULL ) {
|
||||
sqlAppender.appendSql( " and " );
|
||||
arg.accept( translator );
|
||||
sqlAppender.appendSql( " is not null" );
|
||||
}
|
||||
sqlAppender.appendSql( ')' );
|
||||
translator.getCurrentClauseStack().pop();
|
||||
}
|
||||
else if ( nullBehavior != JsonNullBehavior.NULL ) {
|
||||
sqlAppender.appendSql( " filter (where " );
|
||||
arg.accept( translator );
|
||||
sqlAppender.appendSql( " is not null)" );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -61,6 +61,10 @@ public class PostgreSQLJsonArrayFunction extends JsonArrayFunction {
|
|||
else {
|
||||
sqlAppender.appendSql( "to_jsonb(" );
|
||||
node.accept( walker );
|
||||
if ( node instanceof Literal literal && literal.getJdbcMapping().getJdbcType().isString() ) {
|
||||
// PostgreSQL until version 16 is not smart enough to infer the type of a string literal
|
||||
sqlAppender.appendSql( "::text" );
|
||||
}
|
||||
sqlAppender.appendSql( ')' );
|
||||
}
|
||||
sqlAppender.appendSql( ')' );
|
||||
|
|
|
@ -0,0 +1,82 @@
|
|||
/*
|
||||
* 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.Clause;
|
||||
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.JsonNullBehavior;
|
||||
import org.hibernate.sql.ast.tree.expression.JsonObjectAggUniqueKeysBehavior;
|
||||
import org.hibernate.sql.ast.tree.predicate.Predicate;
|
||||
import org.hibernate.type.SqlTypes;
|
||||
import org.hibernate.type.spi.TypeConfiguration;
|
||||
|
||||
/**
|
||||
* PostgreSQL json_objectagg function.
|
||||
*/
|
||||
public class PostgreSQLJsonObjectAggFunction extends JsonObjectAggFunction {
|
||||
|
||||
private final boolean supportsStandard;
|
||||
|
||||
public PostgreSQLJsonObjectAggFunction(boolean supportsStandard, TypeConfiguration typeConfiguration) {
|
||||
super( ":", true, typeConfiguration );
|
||||
this.supportsStandard = supportsStandard;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void render(
|
||||
SqlAppender sqlAppender,
|
||||
JsonObjectAggArguments arguments,
|
||||
Predicate filter,
|
||||
ReturnableType<?> returnType,
|
||||
SqlAstTranslator<?> translator) {
|
||||
if ( supportsStandard ) {
|
||||
super.render( sqlAppender, arguments, filter, returnType, translator );
|
||||
}
|
||||
else {
|
||||
if ( arguments.uniqueKeysBehavior() == JsonObjectAggUniqueKeysBehavior.WITH ) {
|
||||
throw new QueryException( "Can't emulate json_objectagg 'with unique keys' clause." );
|
||||
}
|
||||
final String jsonTypeName = translator.getSessionFactory().getTypeConfiguration().getDdlTypeRegistry()
|
||||
.getTypeName( SqlTypes.JSON, translator.getSessionFactory().getJdbcServices().getDialect() );
|
||||
sqlAppender.appendSql( jsonTypeName );
|
||||
sqlAppender.appendSql( "_object_agg" );
|
||||
sqlAppender.appendSql( '(' );
|
||||
arguments.key().accept( translator );
|
||||
sqlAppender.appendSql( ',' );
|
||||
arguments.value().accept( translator );
|
||||
sqlAppender.appendSql( ')' );
|
||||
|
||||
if ( filter != null ) {
|
||||
translator.getCurrentClauseStack().push( Clause.WHERE );
|
||||
sqlAppender.appendSql( " filter (where " );
|
||||
filter.accept( translator );
|
||||
if ( arguments.nullBehavior() != JsonNullBehavior.NULL ) {
|
||||
sqlAppender.appendSql( " and " );
|
||||
arguments.value().accept( translator );
|
||||
sqlAppender.appendSql( " is not null" );
|
||||
}
|
||||
sqlAppender.appendSql( ')' );
|
||||
translator.getCurrentClauseStack().pop();
|
||||
}
|
||||
else if ( arguments.nullBehavior() != JsonNullBehavior.NULL ) {
|
||||
sqlAppender.appendSql( " filter (where " );
|
||||
arguments.value().accept( translator );
|
||||
sqlAppender.appendSql( " is not null)" );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void renderUniqueAndReturningClause(SqlAppender sqlAppender, JsonObjectAggArguments arguments, SqlAstTranslator<?> translator) {
|
||||
renderUniqueClause( sqlAppender, arguments, translator );
|
||||
renderReturningClause( sqlAppender, arguments, translator );
|
||||
}
|
||||
}
|
|
@ -62,6 +62,10 @@ public class PostgreSQLJsonObjectFunction extends JsonObjectFunction {
|
|||
else {
|
||||
sqlAppender.appendSql( "to_jsonb(" );
|
||||
value.accept( walker );
|
||||
if ( value instanceof Literal literal && literal.getJdbcMapping().getJdbcType().isString() ) {
|
||||
// PostgreSQL until version 16 is not smart enough to infer the type of a string literal
|
||||
sqlAppender.appendSql( "::text" );
|
||||
}
|
||||
sqlAppender.appendSql( ')' );
|
||||
}
|
||||
sqlAppender.appendSql( ')' );
|
||||
|
|
|
@ -0,0 +1,80 @@
|
|||
/*
|
||||
* 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.Clause;
|
||||
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.JsonNullBehavior;
|
||||
import org.hibernate.sql.ast.tree.expression.JsonObjectAggUniqueKeysBehavior;
|
||||
import org.hibernate.sql.ast.tree.predicate.Predicate;
|
||||
import org.hibernate.type.spi.TypeConfiguration;
|
||||
|
||||
/**
|
||||
* SQL Server json_objectagg function.
|
||||
*/
|
||||
public class SQLServerJsonObjectAggFunction extends JsonObjectAggFunction {
|
||||
|
||||
public SQLServerJsonObjectAggFunction(TypeConfiguration typeConfiguration) {
|
||||
super( ",", false, typeConfiguration );
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void render(
|
||||
SqlAppender sqlAppender,
|
||||
JsonObjectAggArguments arguments,
|
||||
Predicate filter,
|
||||
ReturnableType<?> returnType,
|
||||
SqlAstTranslator<?> translator) {
|
||||
final boolean caseWrapper = filter != null;
|
||||
if ( arguments.uniqueKeysBehavior() == JsonObjectAggUniqueKeysBehavior.WITH ) {
|
||||
throw new QueryException( "Can't emulate json_objectagg 'with unique keys' clause." );
|
||||
}
|
||||
sqlAppender.appendSql( "'{'+string_agg(" );
|
||||
renderArgument( sqlAppender, arguments.key(), arguments.nullBehavior(), translator );
|
||||
sqlAppender.appendSql( "+':'+" );
|
||||
if ( caseWrapper ) {
|
||||
if ( arguments.nullBehavior() != JsonNullBehavior.ABSENT ) {
|
||||
throw new QueryException( "Can't emulate json_objectagg filter clause when using 'null on null' clause." );
|
||||
}
|
||||
translator.getCurrentClauseStack().push( Clause.WHERE );
|
||||
sqlAppender.appendSql( "case when " );
|
||||
filter.accept( translator );
|
||||
translator.getCurrentClauseStack().pop();
|
||||
sqlAppender.appendSql( " then " );
|
||||
renderArgument( sqlAppender, arguments.value(), arguments.nullBehavior(), translator );
|
||||
sqlAppender.appendSql( " else null end)" );
|
||||
}
|
||||
else {
|
||||
renderArgument( sqlAppender, arguments.value(), arguments.nullBehavior(), translator );
|
||||
}
|
||||
sqlAppender.appendSql( ",',')+'}'" );
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void renderArgument(
|
||||
SqlAppender sqlAppender,
|
||||
Expression arg,
|
||||
JsonNullBehavior nullBehavior,
|
||||
SqlAstTranslator<?> translator) {
|
||||
// Convert SQL type to JSON type
|
||||
if ( nullBehavior != JsonNullBehavior.NULL ) {
|
||||
sqlAppender.appendSql( "nullif(" );
|
||||
}
|
||||
sqlAppender.appendSql( "substring(json_array(" );
|
||||
arg.accept( translator );
|
||||
sqlAppender.appendSql( " null on null),2,len(json_array(" );
|
||||
arg.accept( translator );
|
||||
sqlAppender.appendSql( " null on null))-2)" );
|
||||
if ( nullBehavior != JsonNullBehavior.NULL ) {
|
||||
sqlAppender.appendSql( ",'null')" );
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3847,6 +3847,70 @@ public interface HibernateCriteriaBuilder extends CriteriaBuilder {
|
|||
@Incubating
|
||||
JpaExpression<String> jsonArrayAggWithNulls(Expression<?> value, Predicate filter, JpaOrder... orderBy);
|
||||
|
||||
/**
|
||||
* Aggregates the given value under the given key into a JSON object.
|
||||
*
|
||||
* @since 7.0
|
||||
*/
|
||||
@Incubating
|
||||
JpaExpression<String> jsonObjectAgg(Expression<?> key, Expression<?> value);
|
||||
|
||||
/**
|
||||
* Aggregates the given value under the given key into a JSON object, retaining {@code null} values in the JSON object.
|
||||
*
|
||||
* @since 7.0
|
||||
*/
|
||||
@Incubating
|
||||
JpaExpression<String> jsonObjectAggWithNulls(Expression<?> key, Expression<?> value);
|
||||
|
||||
/**
|
||||
* Aggregates the given value under the given key into a JSON object.
|
||||
*
|
||||
* @since 7.0
|
||||
*/
|
||||
@Incubating
|
||||
JpaExpression<String> jsonObjectAggWithUniqueKeys(Expression<?> key, Expression<?> value);
|
||||
|
||||
/**
|
||||
* Aggregates the given value under the given key into a JSON object, retaining {@code null} values in the JSON object.
|
||||
*
|
||||
* @since 7.0
|
||||
*/
|
||||
@Incubating
|
||||
JpaExpression<String> jsonObjectAggWithUniqueKeysAndNulls(Expression<?> key, Expression<?> value);
|
||||
|
||||
/**
|
||||
* Aggregates the given value under the given key into a JSON object.
|
||||
*
|
||||
* @since 7.0
|
||||
*/
|
||||
@Incubating
|
||||
JpaExpression<String> jsonObjectAgg(Expression<?> key, Expression<?> value, Predicate filter);
|
||||
|
||||
/**
|
||||
* Aggregates the given value under the given key into a JSON object, retaining {@code null} values in the JSON object.
|
||||
*
|
||||
* @since 7.0
|
||||
*/
|
||||
@Incubating
|
||||
JpaExpression<String> jsonObjectAggWithNulls(Expression<?> key, Expression<?> value, Predicate filter);
|
||||
|
||||
/**
|
||||
* Aggregates the given value under the given key into a JSON object.
|
||||
*
|
||||
* @since 7.0
|
||||
*/
|
||||
@Incubating
|
||||
JpaExpression<String> jsonObjectAggWithUniqueKeys(Expression<?> key, Expression<?> value, Predicate filter);
|
||||
|
||||
/**
|
||||
* Aggregates the given value under the given key into a JSON object, retaining {@code null} values in the JSON object.
|
||||
*
|
||||
* @since 7.0
|
||||
*/
|
||||
@Incubating
|
||||
JpaExpression<String> jsonObjectAggWithUniqueKeysAndNulls(Expression<?> key, Expression<?> value, Predicate filter);
|
||||
|
||||
@Override
|
||||
JpaPredicate and(List<Predicate> restrictions);
|
||||
|
||||
|
|
|
@ -3472,4 +3472,55 @@ public class HibernateCriteriaBuilderDelegate implements HibernateCriteriaBuilde
|
|||
public JpaExpression<String> jsonArrayAggWithNulls(Expression<?> value, Predicate filter, JpaOrder... orderBy) {
|
||||
return criteriaBuilder.jsonArrayAggWithNulls( value, filter, orderBy );
|
||||
}
|
||||
|
||||
@Override
|
||||
@Incubating
|
||||
public JpaExpression<String> jsonObjectAgg(Expression<?> key, Expression<?> value) {
|
||||
return criteriaBuilder.jsonObjectAgg( key, value );
|
||||
}
|
||||
|
||||
@Override
|
||||
@Incubating
|
||||
public JpaExpression<String> jsonObjectAggWithNulls(Expression<?> key, Expression<?> value) {
|
||||
return criteriaBuilder.jsonObjectAggWithNulls( key, value );
|
||||
}
|
||||
|
||||
@Override
|
||||
@Incubating
|
||||
public JpaExpression<String> jsonObjectAggWithUniqueKeys(Expression<?> key, Expression<?> value) {
|
||||
return criteriaBuilder.jsonObjectAggWithUniqueKeys( key, value );
|
||||
}
|
||||
|
||||
@Override
|
||||
@Incubating
|
||||
public JpaExpression<String> jsonObjectAggWithUniqueKeysAndNulls(Expression<?> key, Expression<?> value) {
|
||||
return criteriaBuilder.jsonObjectAggWithUniqueKeysAndNulls( key, value );
|
||||
}
|
||||
|
||||
@Override
|
||||
@Incubating
|
||||
public JpaExpression<String> jsonObjectAgg(Expression<?> key, Expression<?> value, Predicate filter) {
|
||||
return criteriaBuilder.jsonObjectAgg( key, value, filter );
|
||||
}
|
||||
|
||||
@Override
|
||||
@Incubating
|
||||
public JpaExpression<String> jsonObjectAggWithNulls(Expression<?> key, Expression<?> value, Predicate filter) {
|
||||
return criteriaBuilder.jsonObjectAggWithNulls( key, value, filter );
|
||||
}
|
||||
|
||||
@Override
|
||||
@Incubating
|
||||
public JpaExpression<String> jsonObjectAggWithUniqueKeys(Expression<?> key, Expression<?> value, Predicate filter) {
|
||||
return criteriaBuilder.jsonObjectAggWithUniqueKeys( key, value, filter );
|
||||
}
|
||||
|
||||
@Override
|
||||
@Incubating
|
||||
public JpaExpression<String> jsonObjectAggWithUniqueKeysAndNulls(
|
||||
Expression<?> key,
|
||||
Expression<?> value,
|
||||
Predicate filter) {
|
||||
return criteriaBuilder.jsonObjectAggWithUniqueKeysAndNulls( key, value, filter );
|
||||
}
|
||||
}
|
||||
|
|
|
@ -146,6 +146,7 @@ import org.hibernate.query.sqm.tree.expression.SqmFunction;
|
|||
import org.hibernate.query.sqm.tree.expression.SqmHqlNumericLiteral;
|
||||
import org.hibernate.query.sqm.tree.expression.SqmJsonExistsExpression;
|
||||
import org.hibernate.query.sqm.tree.expression.SqmJsonNullBehavior;
|
||||
import org.hibernate.query.sqm.tree.expression.SqmJsonObjectAggUniqueKeysBehavior;
|
||||
import org.hibernate.query.sqm.tree.expression.SqmJsonQueryExpression;
|
||||
import org.hibernate.query.sqm.tree.expression.SqmJsonValueExpression;
|
||||
import org.hibernate.query.sqm.tree.expression.SqmLiteral;
|
||||
|
@ -2931,6 +2932,38 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
|
|||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object visitJsonObjectAggFunction(HqlParser.JsonObjectAggFunctionContext ctx) {
|
||||
final HqlParser.JsonNullClauseContext jsonNullClauseContext = ctx.jsonNullClause();
|
||||
final HqlParser.JsonUniqueKeysClauseContext jsonUniqueKeysClauseContext = ctx.jsonUniqueKeysClause();
|
||||
final ArrayList<SqmTypedNode<?>> arguments = new ArrayList<>( 4 );
|
||||
for ( HqlParser.ExpressionOrPredicateContext subCtx : ctx.expressionOrPredicate() ) {
|
||||
arguments.add( (SqmTypedNode<?>) subCtx.accept( this ) );
|
||||
}
|
||||
if ( jsonNullClauseContext != null ) {
|
||||
final TerminalNode firstToken = (TerminalNode) jsonNullClauseContext.getChild( 0 );
|
||||
arguments.add(
|
||||
firstToken.getSymbol().getType() == HqlParser.ABSENT
|
||||
? SqmJsonNullBehavior.ABSENT
|
||||
: SqmJsonNullBehavior.NULL
|
||||
);
|
||||
}
|
||||
if ( jsonUniqueKeysClauseContext != null ) {
|
||||
final TerminalNode firstToken = (TerminalNode) jsonUniqueKeysClauseContext.getChild( 0 );
|
||||
arguments.add(
|
||||
firstToken.getSymbol().getType() == HqlParser.WITH
|
||||
? SqmJsonObjectAggUniqueKeysBehavior.WITH
|
||||
: SqmJsonObjectAggUniqueKeysBehavior.WITHOUT
|
||||
);
|
||||
}
|
||||
return getFunctionDescriptor( "json_objectagg" ).generateAggregateSqmExpression(
|
||||
arguments,
|
||||
getFilterExpression( ctx ),
|
||||
null,
|
||||
creationContext.getQueryEngine()
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SqmPredicate visitIncludesPredicate(HqlParser.IncludesPredicateContext ctx) {
|
||||
final boolean negated = ctx.NOT() != null;
|
||||
|
|
|
@ -679,6 +679,30 @@ public interface NodeBuilder extends HibernateCriteriaBuilder, BindingContext {
|
|||
@Override
|
||||
SqmExpression<String> jsonArrayAgg(Expression<?> value, JpaOrder... orderBy);
|
||||
|
||||
@Override
|
||||
SqmExpression<String> jsonObjectAggWithUniqueKeysAndNulls(Expression<?> key, Expression<?> value);
|
||||
|
||||
@Override
|
||||
SqmExpression<String> jsonObjectAggWithUniqueKeys(Expression<?> key, Expression<?> value);
|
||||
|
||||
@Override
|
||||
SqmExpression<String> jsonObjectAggWithNulls(Expression<?> key, Expression<?> value);
|
||||
|
||||
@Override
|
||||
SqmExpression<String> jsonObjectAgg(Expression<?> key, Expression<?> value);
|
||||
|
||||
@Override
|
||||
SqmExpression<String> jsonObjectAggWithUniqueKeysAndNulls(Expression<?> key, Expression<?> value, Predicate filter);
|
||||
|
||||
@Override
|
||||
SqmExpression<String> jsonObjectAggWithUniqueKeys(Expression<?> key, Expression<?> value, Predicate filter);
|
||||
|
||||
@Override
|
||||
SqmExpression<String> jsonObjectAggWithNulls(Expression<?> key, Expression<?> value, Predicate filter);
|
||||
|
||||
@Override
|
||||
SqmExpression<String> jsonObjectAgg(Expression<?> key, Expression<?> value, Predicate filter);
|
||||
|
||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// Covariant overrides
|
||||
|
||||
|
|
|
@ -122,6 +122,7 @@ import org.hibernate.query.sqm.tree.expression.SqmFormat;
|
|||
import org.hibernate.query.sqm.tree.expression.SqmFunction;
|
||||
import org.hibernate.query.sqm.tree.expression.SqmJsonExistsExpression;
|
||||
import org.hibernate.query.sqm.tree.expression.SqmJsonNullBehavior;
|
||||
import org.hibernate.query.sqm.tree.expression.SqmJsonObjectAggUniqueKeysBehavior;
|
||||
import org.hibernate.query.sqm.tree.expression.SqmJsonQueryExpression;
|
||||
import org.hibernate.query.sqm.tree.expression.SqmJsonValueExpression;
|
||||
import org.hibernate.query.sqm.tree.expression.SqmLiteral;
|
||||
|
@ -5433,6 +5434,72 @@ public class SqmCriteriaNodeBuilder implements NodeBuilder, Serializable {
|
|||
return jsonArrayAgg( (SqmExpression<?>) value, SqmJsonNullBehavior.NULL, null, orderByClause( orderBy ) );
|
||||
}
|
||||
|
||||
@Override
|
||||
public SqmExpression<String> jsonObjectAggWithUniqueKeysAndNulls(Expression<?> key, Expression<?> value) {
|
||||
return jsonObjectAgg( key, value, SqmJsonNullBehavior.NULL, SqmJsonObjectAggUniqueKeysBehavior.WITH, null );
|
||||
}
|
||||
|
||||
@Override
|
||||
public SqmExpression<String> jsonObjectAggWithUniqueKeys(Expression<?> key, Expression<?> value) {
|
||||
return jsonObjectAgg( key, value, null, SqmJsonObjectAggUniqueKeysBehavior.WITH, null );
|
||||
}
|
||||
|
||||
@Override
|
||||
public SqmExpression<String> jsonObjectAggWithNulls(Expression<?> key, Expression<?> value) {
|
||||
return jsonObjectAgg( key, value, SqmJsonNullBehavior.NULL, null, null );
|
||||
}
|
||||
|
||||
@Override
|
||||
public SqmExpression<String> jsonObjectAgg(Expression<?> key, Expression<?> value) {
|
||||
return jsonObjectAgg( key, value, null, null, null );
|
||||
}
|
||||
|
||||
@Override
|
||||
public SqmExpression<String> jsonObjectAggWithUniqueKeysAndNulls(
|
||||
Expression<?> key,
|
||||
Expression<?> value,
|
||||
Predicate filter) {
|
||||
return jsonObjectAgg( key, value, SqmJsonNullBehavior.NULL, SqmJsonObjectAggUniqueKeysBehavior.WITH, filter );
|
||||
}
|
||||
|
||||
@Override
|
||||
public SqmExpression<String> jsonObjectAggWithUniqueKeys(Expression<?> key, Expression<?> value, Predicate filter) {
|
||||
return jsonObjectAgg( key, value, null, SqmJsonObjectAggUniqueKeysBehavior.WITH, filter );
|
||||
}
|
||||
|
||||
@Override
|
||||
public SqmExpression<String> jsonObjectAggWithNulls(Expression<?> key, Expression<?> value, Predicate filter) {
|
||||
return jsonObjectAgg( key, value, SqmJsonNullBehavior.NULL, null, filter );
|
||||
}
|
||||
|
||||
@Override
|
||||
public SqmExpression<String> jsonObjectAgg(Expression<?> key, Expression<?> value, Predicate filter) {
|
||||
return jsonObjectAgg( key, value, null, null, filter );
|
||||
}
|
||||
|
||||
private SqmExpression<String> jsonObjectAgg(
|
||||
Expression<?> key,
|
||||
Expression<?> value,
|
||||
@Nullable SqmJsonNullBehavior nullBehavior,
|
||||
@Nullable SqmJsonObjectAggUniqueKeysBehavior uniqueKeysBehavior,
|
||||
@Nullable Predicate filterPredicate) {
|
||||
final ArrayList<SqmTypedNode<?>> arguments = new ArrayList<>( 4 );
|
||||
arguments.add( (SqmTypedNode<?>) key );
|
||||
arguments.add( (SqmTypedNode<?>) value );
|
||||
if ( nullBehavior != null ) {
|
||||
arguments.add( nullBehavior );
|
||||
}
|
||||
if ( uniqueKeysBehavior != null ) {
|
||||
arguments.add( uniqueKeysBehavior );
|
||||
}
|
||||
return getFunctionDescriptor( "json_objectagg" ).generateAggregateSqmExpression(
|
||||
arguments,
|
||||
(SqmPredicate) filterPredicate,
|
||||
null,
|
||||
queryEngine
|
||||
);
|
||||
}
|
||||
|
||||
private @Nullable SqmOrderByClause orderByClause(JpaOrder[] orderBy) {
|
||||
if ( orderBy.length == 0 ) {
|
||||
return null;
|
||||
|
|
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
* 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 org.hibernate.query.sqm.NodeBuilder;
|
||||
import org.hibernate.query.sqm.SemanticQueryWalker;
|
||||
import org.hibernate.query.sqm.SqmExpressible;
|
||||
import org.hibernate.query.sqm.tree.SqmCopyContext;
|
||||
import org.hibernate.query.sqm.tree.SqmTypedNode;
|
||||
import org.hibernate.sql.ast.tree.expression.JsonObjectAggUniqueKeysBehavior;
|
||||
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
/**
|
||||
* Specifies if a {@code json_objectagg} may aggregate duplicate keys.
|
||||
*
|
||||
* @since 7.0
|
||||
*/
|
||||
public enum SqmJsonObjectAggUniqueKeysBehavior implements SqmTypedNode<Object> {
|
||||
/**
|
||||
* Aggregate only unique keys. Fail aggregation if a duplicate is encountered.
|
||||
*/
|
||||
WITH,
|
||||
/**
|
||||
* Aggregate duplicate keys without failing.
|
||||
*/
|
||||
WITHOUT;
|
||||
|
||||
@Override
|
||||
public @Nullable SqmExpressible<Object> getNodeType() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public NodeBuilder nodeBuilder() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SqmJsonObjectAggUniqueKeysBehavior copy(SqmCopyContext context) {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <X> X accept(SemanticQueryWalker<X> walker) {
|
||||
//noinspection unchecked
|
||||
return (X) (this == WITH ? JsonObjectAggUniqueKeysBehavior.WITH : JsonObjectAggUniqueKeysBehavior.WITHOUT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void appendHqlString(StringBuilder sb) {
|
||||
if ( this == WITH ) {
|
||||
sb.append( " with unique keys" );
|
||||
}
|
||||
else {
|
||||
sb.append( " without unique keys" );
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* 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 JsonObjectAggUniqueKeysBehavior implements SqlAstNode {
|
||||
WITH,
|
||||
WITHOUT;
|
||||
|
||||
@Override
|
||||
public void accept(SqlAstWalker sqlTreeWalker) {
|
||||
throw new UnsupportedOperationException("JsonObjectAggUniqueKeysBehavior doesn't support walking");
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
* 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 org.hibernate.dialect.CockroachDialect;
|
||||
import org.hibernate.dialect.DB2Dialect;
|
||||
import org.hibernate.dialect.HANADialect;
|
||||
import org.hibernate.dialect.MySQLDialect;
|
||||
import org.hibernate.dialect.PostgreSQLDialect;
|
||||
import org.hibernate.dialect.SQLServerDialect;
|
||||
|
||||
import org.hibernate.testing.orm.domain.StandardDomainModel;
|
||||
import org.hibernate.testing.orm.junit.DialectFeatureChecks;
|
||||
import org.hibernate.testing.orm.junit.DomainModel;
|
||||
import org.hibernate.testing.orm.junit.RequiresDialectFeature;
|
||||
import org.hibernate.testing.orm.junit.SessionFactory;
|
||||
import org.hibernate.testing.orm.junit.SessionFactoryScope;
|
||||
import org.hibernate.testing.orm.junit.SkipForDialect;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
* @author Christian Beikov
|
||||
*/
|
||||
@DomainModel(standardModels = StandardDomainModel.GAMBIT)
|
||||
@SessionFactory
|
||||
@RequiresDialectFeature( feature = DialectFeatureChecks.SupportsJsonObjectAgg.class)
|
||||
public class JsonObjectAggregateTest {
|
||||
|
||||
@Test
|
||||
public void testSimple(SessionFactoryScope scope) {
|
||||
scope.inSession( em -> {
|
||||
//tag::hql-json-objectagg-example[]
|
||||
em.createQuery( "select json_objectagg(e.theString value e.id) from EntityOfBasics e" ).getResultList();
|
||||
//end::hql-json-objectagg-example[]
|
||||
} );
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNull(SessionFactoryScope scope) {
|
||||
scope.inSession( em -> {
|
||||
//tag::hql-json-objectagg-null-example[]
|
||||
em.createQuery( "select json_objectagg(e.theString : e.id null on null) from EntityOfBasics e" ).getResultList();
|
||||
//end::hql-json-objectagg-null-example[]
|
||||
} );
|
||||
}
|
||||
|
||||
@Test
|
||||
@SkipForDialect(dialectClass = MySQLDialect.class, matchSubTypes = true, reason = "MySQL has no way to throw an error on duplicate json object keys. The last one always wins.")
|
||||
@SkipForDialect(dialectClass = SQLServerDialect.class, reason = "SQL Server has no way to throw an error on duplicate json object keys.")
|
||||
@SkipForDialect(dialectClass = HANADialect.class, reason = "HANA has no way to throw an error on duplicate json object keys.")
|
||||
@SkipForDialect(dialectClass = DB2Dialect.class, reason = "DB2 has no way to throw an error on duplicate json object keys.")
|
||||
@SkipForDialect(dialectClass = CockroachDialect.class, reason = "CockroachDB has no way to throw an error on duplicate json object keys.")
|
||||
@SkipForDialect(dialectClass = PostgreSQLDialect.class, majorVersion = 15, matchSubTypes = true, reason = "CockroachDB has no way to throw an error on duplicate json object keys.")
|
||||
public void testUniqueKeys(SessionFactoryScope scope) {
|
||||
scope.inSession( em -> {
|
||||
//tag::hql-json-objectagg-unique-keys-example[]
|
||||
em.createQuery( "select json_objectagg(e.theString : e.id with unique keys) from EntityOfBasics e" ).getResultList();
|
||||
//end::hql-json-objectagg-unique-keys-example[]
|
||||
} );
|
||||
}
|
||||
|
||||
}
|
|
@ -12,10 +12,19 @@ import java.util.HashMap;
|
|||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.hibernate.HibernateException;
|
||||
import org.hibernate.JDBCException;
|
||||
import org.hibernate.annotations.JdbcTypeCode;
|
||||
import org.hibernate.dialect.CockroachDialect;
|
||||
import org.hibernate.dialect.DB2Dialect;
|
||||
import org.hibernate.dialect.HANADialect;
|
||||
import org.hibernate.dialect.HSQLDialect;
|
||||
import org.hibernate.dialect.MySQLDialect;
|
||||
import org.hibernate.dialect.OracleDialect;
|
||||
import org.hibernate.dialect.PostgreSQLDialect;
|
||||
import org.hibernate.dialect.SQLServerDialect;
|
||||
import org.hibernate.type.SqlTypes;
|
||||
|
||||
import org.hibernate.testing.orm.domain.gambit.EntityOfBasics;
|
||||
|
@ -49,8 +58,10 @@ import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
|||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
|
||||
@DomainModel( annotatedClasses = {
|
||||
JsonFunctionTests.JsonHolder.class,
|
||||
|
@ -89,9 +100,12 @@ public class JsonFunctionTests {
|
|||
EntityOfBasics e1 = new EntityOfBasics();
|
||||
e1.setId( 1 );
|
||||
e1.setTheString( "Dog" );
|
||||
e1.setTheInteger( 0 );
|
||||
e1.setTheUuid( UUID.randomUUID() );
|
||||
EntityOfBasics e2 = new EntityOfBasics();
|
||||
e2.setId( 2 );
|
||||
e2.setTheString( "Cat" );
|
||||
e2.setTheInteger( 0 );
|
||||
|
||||
em.persist( e1 );
|
||||
em.persist( e2 );
|
||||
|
@ -266,7 +280,7 @@ public class JsonFunctionTests {
|
|||
assertEquals( entity.json.get( "theFloat" ), Double.parseDouble( nested.get( "theFloat" ).toString() ) );
|
||||
assertEquals( entity.json.get( "theString" ), nested.get( "theString" ) );
|
||||
assertEquals( entity.json.get( "theBoolean" ), nested.get( "theBoolean" ) );
|
||||
// HSQLDB bug
|
||||
// HSQLDB bug: https://sourceforge.net/p/hsqldb/bugs/1720/
|
||||
if ( !( DialectContext.getDialect() instanceof HSQLDialect ) ) {
|
||||
assertFalse( nested.containsKey( "theNull" ) );
|
||||
}
|
||||
|
@ -368,6 +382,86 @@ public class JsonFunctionTests {
|
|||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsJsonObjectAgg.class)
|
||||
public void testJsonObjectAgg(SessionFactoryScope scope) {
|
||||
scope.inTransaction(
|
||||
session -> {
|
||||
String jsonArray = session.createQuery(
|
||||
"select json_objectagg(e.theString value e.id) " +
|
||||
"from EntityOfBasics e",
|
||||
String.class
|
||||
).getSingleResult();
|
||||
Map<String, Object> object = parseObject( jsonArray );
|
||||
assertEquals( 2, object.size() );
|
||||
assertEquals( 1, object.get( "Dog" ) );
|
||||
assertEquals( 2, object.get( "Cat" ) );
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsJsonObjectAgg.class)
|
||||
public void testJsonObjectAggNullFilter(SessionFactoryScope scope) {
|
||||
scope.inTransaction(
|
||||
session -> {
|
||||
String jsonArray = session.createQuery(
|
||||
"select json_objectagg(e.theString value e.theUuid) " +
|
||||
"from EntityOfBasics e",
|
||||
String.class
|
||||
).getSingleResult();
|
||||
Map<String, Object> object = parseObject( jsonArray );
|
||||
assertEquals( 1, object.size() );
|
||||
assertTrue( object.containsKey( "Dog" ) );
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsJsonObjectAgg.class)
|
||||
public void testJsonObjectAggNullClause(SessionFactoryScope scope) {
|
||||
scope.inTransaction(
|
||||
session -> {
|
||||
String jsonArray = session.createQuery(
|
||||
"select json_objectagg(e.theString value e.theUuid null on null) " +
|
||||
"from EntityOfBasics e",
|
||||
String.class
|
||||
).getSingleResult();
|
||||
Map<String, Object> object = parseObject( jsonArray );
|
||||
assertEquals( 2, object.size() );
|
||||
assertNotNull( object.get( "Dog" ) );
|
||||
assertNull( object.get( "Cat" ) );
|
||||
assertTrue( object.containsKey( "Cat" ) );
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsJsonObjectAgg.class)
|
||||
@SkipForDialect(dialectClass = MySQLDialect.class, matchSubTypes = true, reason = "MySQL has no way to throw an error on duplicate json object keys. The last one always wins.")
|
||||
@SkipForDialect(dialectClass = SQLServerDialect.class, reason = "SQL Server has no way to throw an error on duplicate json object keys.")
|
||||
@SkipForDialect(dialectClass = HANADialect.class, reason = "HANA has no way to throw an error on duplicate json object keys.")
|
||||
@SkipForDialect(dialectClass = DB2Dialect.class, reason = "DB2 has no way to throw an error on duplicate json object keys.")
|
||||
@SkipForDialect(dialectClass = CockroachDialect.class, reason = "CockroachDB has no way to throw an error on duplicate json object keys.")
|
||||
@SkipForDialect(dialectClass = PostgreSQLDialect.class, majorVersion = 15, matchSubTypes = true, reason = "CockroachDB has no way to throw an error on duplicate json object keys.")
|
||||
public void testJsonObjectAggUniqueKeys(SessionFactoryScope scope) {
|
||||
scope.inTransaction(
|
||||
session -> {
|
||||
try {
|
||||
session.createQuery(
|
||||
"select json_objectagg(str(e.theInteger) value e.theString with unique keys) " +
|
||||
"from EntityOfBasics e",
|
||||
String.class
|
||||
).getSingleResult();
|
||||
fail("Should fail because keys are not unique");
|
||||
}
|
||||
catch (HibernateException e) {
|
||||
assertInstanceOf( JDBCException.class, e );
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private static final ObjectMapper MAPPER = new ObjectMapper();
|
||||
|
||||
private static Map<String, Object> parseObject(String json) {
|
||||
|
|
|
@ -785,6 +785,14 @@ abstract public class DialectFeatureChecks {
|
|||
}
|
||||
}
|
||||
|
||||
public static class SupportsJsonObjectAgg implements DialectFeatureCheck {
|
||||
public boolean apply(Dialect dialect) {
|
||||
return definesFunction( dialect, "json_objectagg" )
|
||||
// Bug in HSQL: https://sourceforge.net/p/hsqldb/bugs/1718/
|
||||
&& !( dialect instanceof HSQLDialect );
|
||||
}
|
||||
}
|
||||
|
||||
public static class IsJtds implements DialectFeatureCheck {
|
||||
public boolean apply(Dialect dialect) {
|
||||
return dialect instanceof SybaseDialect && ( (SybaseDialect) dialect ).getDriverKind() == SybaseDriverKind.JTDS;
|
||||
|
|
Loading…
Reference in New Issue