HHH-18496 Add json_arrayagg
This commit is contained in:
parent
6b4cc28f0e
commit
c58485c4ef
|
@ -1634,6 +1634,7 @@ The following functions deal with SQL JSON types, which are not supported on eve
|
|||
| `json_value()` | Extracts a value from a JSON document by JSON path
|
||||
| `json_exists()` | Checks if a JSON path exists in a JSON document
|
||||
| `json_query()` | Queries non-scalar values by JSON path in a JSON document
|
||||
| `json_arrayagg()` | Creates a JSON array by aggregating values
|
||||
|===
|
||||
|
||||
|
||||
|
@ -1919,6 +1920,49 @@ 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-arrayagg-function]]
|
||||
===== `json_arrayagg()`
|
||||
|
||||
Creates a JSON array by aggregating values.
|
||||
|
||||
[[hql-json-arrayagg-bnf]]
|
||||
[source, antlrv4, indent=0]
|
||||
----
|
||||
include::{extrasdir}/json_arrayagg_bnf.txt[]
|
||||
----
|
||||
|
||||
This aggregate function is similar to an <<hql-aggregate-functions-orderedset,_ordered set aggregate function_>>
|
||||
since it allows to specify the order in which elements are aggregated, but uses a special syntax.
|
||||
|
||||
[[hql-json-arrayagg-example]]
|
||||
====
|
||||
[source, java, indent=0]
|
||||
----
|
||||
include::{json-example-dir-hql}/JsonArrayAggregateTest.java[tags=hql-json-arrayagg-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-arrayagg-null-example]]
|
||||
====
|
||||
[source, java, indent=0]
|
||||
----
|
||||
include::{json-example-dir-hql}/JsonArrayAggregateTest.java[tags=hql-json-arrayagg-null-example]
|
||||
----
|
||||
====
|
||||
|
||||
The order in which elements are aggregated can be defined by specifying an order by clause.
|
||||
|
||||
[[hql-json-arrayagg-order-by-example]]
|
||||
====
|
||||
[source, java, indent=0]
|
||||
----
|
||||
include::{json-example-dir-hql}/JsonArrayAggregateTest.java[tags=hql-json-arrayagg-order-by-example]
|
||||
----
|
||||
====
|
||||
|
||||
[[hql-user-defined-functions]]
|
||||
==== Native and user-defined functions
|
||||
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
"json_arrayagg(" expressionOrPredicate jsonNullClause? orderByClause? ")" filterClause?
|
||||
|
||||
jsonNullClause
|
||||
: ("absent"|"null") "on null"
|
||||
;
|
|
@ -436,6 +436,7 @@ public class DB2LegacyDialect extends Dialect {
|
|||
functionFactory.jsonExists_no_passing();
|
||||
functionFactory.jsonObject_db2();
|
||||
functionFactory.jsonArray_db2();
|
||||
functionFactory.jsonArrayAgg_db2();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -406,6 +406,7 @@ public class H2LegacyDialect extends Dialect {
|
|||
functionFactory.jsonValue_h2();
|
||||
functionFactory.jsonQuery_h2();
|
||||
functionFactory.jsonExists_h2();
|
||||
functionFactory.jsonArrayAgg_h2();
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
|
|
@ -273,6 +273,7 @@ public class HSQLLegacyDialect extends Dialect {
|
|||
if ( getVersion().isSameOrAfter( 2, 7 ) ) {
|
||||
functionFactory.jsonObject_hsqldb();
|
||||
functionFactory.jsonArray_hsqldb();
|
||||
functionFactory.jsonArrayAgg_hsqldb();
|
||||
}
|
||||
|
||||
//trim() requires parameters to be cast when used as trim character
|
||||
|
|
|
@ -92,6 +92,8 @@ public class MariaDBLegacyDialect extends MySQLLegacyDialect {
|
|||
);
|
||||
commonFunctionFactory.jsonValue_mariadb();
|
||||
commonFunctionFactory.jsonArray_mariadb();
|
||||
commonFunctionFactory.jsonQuery_mariadb();
|
||||
commonFunctionFactory.jsonArrayAgg_mariadb();
|
||||
if ( getVersion().isSameOrAfter( 10, 3, 3 ) ) {
|
||||
commonFunctionFactory.inverseDistributionOrderedSetAggregates_windowEmulation();
|
||||
functionContributions.getFunctionRegistry().patternDescriptorBuilder( "median", "median(?1) over ()" )
|
||||
|
|
|
@ -658,6 +658,7 @@ public class MySQLLegacyDialect extends Dialect {
|
|||
functionFactory.jsonExists_mysql();
|
||||
functionFactory.jsonObject_mysql();
|
||||
functionFactory.jsonArray_mysql();
|
||||
functionFactory.jsonArrayAgg_mysql();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -327,6 +327,7 @@ public class OracleLegacyDialect extends Dialect {
|
|||
functionFactory.jsonExists_oracle();
|
||||
functionFactory.jsonObject_oracle();
|
||||
functionFactory.jsonArray_oracle();
|
||||
functionFactory.jsonArrayAgg_oracle();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -638,6 +638,7 @@ public class PostgreSQLLegacyDialect extends Dialect {
|
|||
functionFactory.jsonExists();
|
||||
functionFactory.jsonObject();
|
||||
functionFactory.jsonArray();
|
||||
functionFactory.jsonArrayAgg();
|
||||
}
|
||||
else {
|
||||
functionFactory.jsonValue_postgresql();
|
||||
|
@ -646,10 +647,12 @@ public class PostgreSQLLegacyDialect extends Dialect {
|
|||
if ( getVersion().isSameOrAfter( 16 ) ) {
|
||||
functionFactory.jsonObject();
|
||||
functionFactory.jsonArray();
|
||||
functionFactory.jsonArrayAgg();
|
||||
}
|
||||
else {
|
||||
functionFactory.jsonObject_postgresql();
|
||||
functionFactory.jsonArray_postgresql();
|
||||
functionFactory.jsonArrayAgg_postgresql();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -409,6 +409,7 @@ public class SQLServerLegacyDialect extends AbstractTransactSQLDialect {
|
|||
}
|
||||
if ( getVersion().isSameOrAfter( 14 ) ) {
|
||||
functionFactory.listagg_stringAggWithinGroup( "varchar(max)" );
|
||||
functionFactory.jsonArrayAgg_sqlserver();
|
||||
}
|
||||
if ( getVersion().isSameOrAfter( 16 ) ) {
|
||||
functionFactory.leastGreatest();
|
||||
|
|
|
@ -225,6 +225,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_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_QUERY : [jJ] [sS] [oO] [nN] '_' [qQ] [uU] [eE] [rR] [yY];
|
||||
|
|
|
@ -1627,6 +1627,7 @@ jsonFunction
|
|||
| jsonObjectFunction
|
||||
| jsonQueryFunction
|
||||
| jsonValueFunction
|
||||
| jsonArrayAggFunction
|
||||
;
|
||||
|
||||
/**
|
||||
|
@ -1645,7 +1646,8 @@ jsonValueReturningClause
|
|||
;
|
||||
|
||||
jsonValueOnErrorOrEmptyClause
|
||||
: ( ERROR | NULL | ( DEFAULT expression ) ) ON (ERROR|EMPTY);
|
||||
: ( ERROR | NULL | ( DEFAULT expression ) ) ON (ERROR|EMPTY)
|
||||
;
|
||||
|
||||
/**
|
||||
* The 'json_query()' function
|
||||
|
@ -1695,6 +1697,13 @@ jsonNullClause
|
|||
: (ABSENT|NULL) ON NULL
|
||||
;
|
||||
|
||||
/**
|
||||
* The 'json_arrayagg()' function
|
||||
*/
|
||||
jsonArrayAggFunction
|
||||
: JSON_ARRAYAGG LEFT_PAREN expressionOrPredicate jsonNullClause? orderByClause? RIGHT_PAREN filterClause?
|
||||
;
|
||||
|
||||
/**
|
||||
* Support for "soft" keywords which may be used as identifiers
|
||||
*
|
||||
|
@ -1793,6 +1802,7 @@ jsonNullClause
|
|||
| IS
|
||||
| JOIN
|
||||
| JSON_ARRAY
|
||||
| JSON_ARRAYAGG
|
||||
| JSON_EXISTS
|
||||
| JSON_OBJECT
|
||||
| JSON_QUERY
|
||||
|
|
|
@ -422,6 +422,7 @@ public class DB2Dialect extends Dialect {
|
|||
functionFactory.jsonExists_no_passing();
|
||||
functionFactory.jsonObject_db2();
|
||||
functionFactory.jsonArray_db2();
|
||||
functionFactory.jsonArrayAgg_db2();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -349,6 +349,7 @@ public class H2Dialect extends Dialect {
|
|||
functionFactory.jsonValue_h2();
|
||||
functionFactory.jsonQuery_h2();
|
||||
functionFactory.jsonExists_h2();
|
||||
functionFactory.jsonArrayAgg_h2();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -498,6 +498,7 @@ public class HANADialect extends Dialect {
|
|||
// Introduced in 2.0 SPS 04
|
||||
functionFactory.jsonObject_hana();
|
||||
functionFactory.jsonArray_hana();
|
||||
functionFactory.jsonArrayAgg_hana();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -208,6 +208,7 @@ public class HSQLDialect extends Dialect {
|
|||
if ( getVersion().isSameOrAfter( 2, 7 ) ) {
|
||||
functionFactory.jsonObject_hsqldb();
|
||||
functionFactory.jsonArray_hsqldb();
|
||||
functionFactory.jsonArrayAgg_hsqldb();
|
||||
}
|
||||
|
||||
//trim() requires parameters to be cast when used as trim character
|
||||
|
|
|
@ -95,6 +95,8 @@ public class MariaDBDialect extends MySQLDialect {
|
|||
);
|
||||
commonFunctionFactory.jsonValue_mariadb();
|
||||
commonFunctionFactory.jsonArray_mariadb();
|
||||
commonFunctionFactory.jsonQuery_mariadb();
|
||||
commonFunctionFactory.jsonArrayAgg_mariadb();
|
||||
commonFunctionFactory.inverseDistributionOrderedSetAggregates_windowEmulation();
|
||||
functionContributions.getFunctionRegistry().patternDescriptorBuilder( "median", "median(?1) over ()" )
|
||||
.setInvariantType( functionContributions.getTypeConfiguration().getBasicTypeRegistry().resolve( StandardBasicTypes.DOUBLE ) )
|
||||
|
|
|
@ -643,6 +643,7 @@ public class MySQLDialect extends Dialect {
|
|||
functionFactory.jsonExists_mysql();
|
||||
functionFactory.jsonObject_mysql();
|
||||
functionFactory.jsonArray_mysql();
|
||||
functionFactory.jsonArrayAgg_mysql();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -277,6 +277,7 @@ public class OracleDialect extends Dialect {
|
|||
|
||||
@Override
|
||||
public int getPreferredSqlTypeCodeForBoolean() {
|
||||
// starting 23c we support Boolean type natively
|
||||
return getVersion().isSameOrAfter( 23 ) ? super.getPreferredSqlTypeCodeForBoolean() : Types.BIT;
|
||||
}
|
||||
|
||||
|
@ -404,6 +405,7 @@ public class OracleDialect extends Dialect {
|
|||
functionFactory.jsonExists_oracle();
|
||||
functionFactory.jsonObject_oracle();
|
||||
functionFactory.jsonArray_oracle();
|
||||
functionFactory.jsonArrayAgg_oracle();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -810,10 +812,12 @@ public class OracleDialect extends Dialect {
|
|||
ddlTypeRegistry.addDescriptor( new ArrayDdlTypeImpl( this, false ) );
|
||||
ddlTypeRegistry.addDescriptor( TABLE, new ArrayDdlTypeImpl( this, false ) );
|
||||
|
||||
if(getVersion().isSameOrAfter(23)) {
|
||||
ddlTypeRegistry.addDescriptor(new NamedNativeEnumDdlTypeImpl(this));
|
||||
if ( getVersion().isSameOrAfter( 23 ) ) {
|
||||
ddlTypeRegistry.addDescriptor( new NamedNativeEnumDdlTypeImpl( this ) );
|
||||
ddlTypeRegistry.addDescriptor( new NamedNativeOrdinalEnumDdlTypeImpl( this ) );
|
||||
}
|
||||
// We need the DDL type during runtime to produce the proper encoding in certain functions
|
||||
ddlTypeRegistry.addDescriptor( new DdlTypeImpl( BIT, "number(1,0)", this ) );
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -953,8 +957,7 @@ public class OracleDialect extends Dialect {
|
|||
@Override
|
||||
public void contributeTypes(TypeContributions typeContributions, ServiceRegistry serviceRegistry) {
|
||||
super.contributeTypes( typeContributions, serviceRegistry );
|
||||
if ( getVersion().isBefore( 23 ) ) {
|
||||
// starting 23c we support Boolean type natively
|
||||
if ( ConfigurationHelper.getPreferredSqlTypeCodeForBoolean( serviceRegistry, this ) == BIT ) {
|
||||
typeContributions.contributeJdbcType( OracleBooleanJdbcType.INSTANCE );
|
||||
}
|
||||
typeContributions.contributeJdbcType( OracleXmlJdbcType.INSTANCE );
|
||||
|
|
|
@ -599,6 +599,7 @@ public class PostgreSQLDialect extends Dialect {
|
|||
functionFactory.jsonExists();
|
||||
functionFactory.jsonObject();
|
||||
functionFactory.jsonArray();
|
||||
functionFactory.jsonArrayAgg();
|
||||
}
|
||||
else {
|
||||
functionFactory.jsonValue_postgresql();
|
||||
|
@ -607,10 +608,12 @@ public class PostgreSQLDialect extends Dialect {
|
|||
if ( getVersion().isSameOrAfter( 16 ) ) {
|
||||
functionFactory.jsonObject();
|
||||
functionFactory.jsonArray();
|
||||
functionFactory.jsonArrayAgg();
|
||||
}
|
||||
else {
|
||||
functionFactory.jsonObject_postgresql();
|
||||
functionFactory.jsonArray_postgresql();
|
||||
functionFactory.jsonArrayAgg_postgresql();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -427,6 +427,7 @@ public class SQLServerDialect extends AbstractTransactSQLDialect {
|
|||
}
|
||||
if ( getVersion().isSameOrAfter( 14 ) ) {
|
||||
functionFactory.listagg_stringAggWithinGroup( "varchar(max)" );
|
||||
functionFactory.jsonArrayAgg_sqlserver();
|
||||
}
|
||||
if ( getVersion().isSameOrAfter( 16 ) ) {
|
||||
functionFactory.leastGreatest();
|
||||
|
|
|
@ -77,35 +77,46 @@ import org.hibernate.dialect.function.array.OracleArrayContainsFunction;
|
|||
import org.hibernate.dialect.function.array.PostgreSQLArrayPositionsFunction;
|
||||
import org.hibernate.dialect.function.array.PostgreSQLArrayTrimEmulation;
|
||||
import org.hibernate.dialect.function.json.CockroachDBJsonValueFunction;
|
||||
import org.hibernate.dialect.function.json.DB2JsonArrayAggFunction;
|
||||
import org.hibernate.dialect.function.json.DB2JsonArrayFunction;
|
||||
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.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.HANAJsonObjectFunction;
|
||||
import org.hibernate.dialect.function.json.HSQLJsonArrayAggFunction;
|
||||
import org.hibernate.dialect.function.json.HSQLJsonArrayFunction;
|
||||
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.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.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.MySQLJsonObjectFunction;
|
||||
import org.hibernate.dialect.function.json.MySQLJsonQueryFunction;
|
||||
import org.hibernate.dialect.function.json.MySQLJsonValueFunction;
|
||||
import org.hibernate.dialect.function.json.OracleJsonArrayAggFunction;
|
||||
import org.hibernate.dialect.function.json.OracleJsonArrayFunction;
|
||||
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.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.SQLServerJsonObjectFunction;
|
||||
|
@ -3454,6 +3465,13 @@ public class CommonFunctionFactory {
|
|||
functionRegistry.register( "json_query", new MySQLJsonQueryFunction( typeConfiguration ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* MariaDB json_query() function
|
||||
*/
|
||||
public void jsonQuery_mariadb() {
|
||||
functionRegistry.register( "json_query", new MariaDBJsonQueryFunction( typeConfiguration ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* SQL Server json_query() function
|
||||
*/
|
||||
|
@ -3642,4 +3660,74 @@ public class CommonFunctionFactory {
|
|||
public void jsonArray_postgresql() {
|
||||
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
|
||||
*/
|
||||
public void jsonArrayAgg_h2() {
|
||||
functionRegistry.register( "json_arrayagg", new H2JsonArrayAggFunction( typeConfiguration ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* HSQLDB json_arrayagg() function
|
||||
*/
|
||||
public void jsonArrayAgg_hsqldb() {
|
||||
functionRegistry.register( "json_arrayagg", new HSQLJsonArrayAggFunction( typeConfiguration ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Oracle json_arrayagg() function
|
||||
*/
|
||||
public void jsonArrayAgg_oracle() {
|
||||
functionRegistry.register( "json_arrayagg", new OracleJsonArrayAggFunction( typeConfiguration ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* PostgreSQL json_arrayagg() function
|
||||
*/
|
||||
public void jsonArrayAgg_postgresql() {
|
||||
functionRegistry.register( "json_arrayagg", new PostgreSQLJsonArrayAggFunction( typeConfiguration ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* SQL Server json_arrayagg() function
|
||||
*/
|
||||
public void jsonArrayAgg_sqlserver() {
|
||||
functionRegistry.register( "json_arrayagg", new SQLServerJsonArrayAggFunction( typeConfiguration ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* MySQL json_arrayagg() function
|
||||
*/
|
||||
public void jsonArrayAgg_mysql() {
|
||||
functionRegistry.register( "json_arrayagg", new MySQLJsonArrayAggFunction( typeConfiguration ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* MariaDB json_arrayagg() function
|
||||
*/
|
||||
public void jsonArrayAgg_mariadb() {
|
||||
functionRegistry.register( "json_arrayagg", new MariaDBJsonArrayAggFunction( typeConfiguration ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* DB2 json_arrayagg() function
|
||||
*/
|
||||
public void jsonArrayAgg_db2() {
|
||||
functionRegistry.register( "json_arrayagg", new DB2JsonArrayAggFunction( typeConfiguration ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* HANA json_arrayagg() function
|
||||
*/
|
||||
public void jsonArrayAgg_hana() {
|
||||
functionRegistry.register( "json_arrayagg", new HANAJsonArrayAggFunction( typeConfiguration ) );
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,99 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
|
||||
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
|
||||
*/
|
||||
package org.hibernate.dialect.function.json;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.hibernate.QueryException;
|
||||
import org.hibernate.query.ReturnableType;
|
||||
import org.hibernate.sql.ast.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.predicate.Predicate;
|
||||
import org.hibernate.sql.ast.tree.select.SortSpecification;
|
||||
import org.hibernate.type.spi.TypeConfiguration;
|
||||
|
||||
/**
|
||||
* DB2 json_arrayagg function.
|
||||
*/
|
||||
public class DB2JsonArrayAggFunction extends JsonArrayAggFunction {
|
||||
|
||||
public DB2JsonArrayAggFunction(TypeConfiguration typeConfiguration) {
|
||||
super( false, typeConfiguration );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(
|
||||
SqlAppender sqlAppender,
|
||||
List<? extends SqlAstNode> sqlAstArguments,
|
||||
Predicate filter,
|
||||
List<SortSpecification> withinGroup,
|
||||
ReturnableType<?> returnType,
|
||||
SqlAstTranslator<?> translator) {
|
||||
final boolean caseWrapper = filter != null;
|
||||
sqlAppender.appendSql( "'['||listagg(" );
|
||||
final JsonNullBehavior nullBehavior;
|
||||
if ( sqlAstArguments.size() > 1 ) {
|
||||
nullBehavior = (JsonNullBehavior) sqlAstArguments.get( 1 );
|
||||
}
|
||||
else {
|
||||
nullBehavior = JsonNullBehavior.ABSENT;
|
||||
}
|
||||
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." );
|
||||
}
|
||||
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 );
|
||||
}
|
||||
sqlAppender.appendSql( ",',')" );
|
||||
if ( withinGroup != null && !withinGroup.isEmpty() ) {
|
||||
translator.getCurrentClauseStack().push( Clause.WITHIN_GROUP );
|
||||
sqlAppender.appendSql( " within group (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();
|
||||
sqlAppender.appendSql( ')' );
|
||||
}
|
||||
sqlAppender.appendSql( "||']'" );
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void renderArgument(
|
||||
SqlAppender sqlAppender,
|
||||
Expression arg,
|
||||
JsonNullBehavior nullBehavior,
|
||||
SqlAstTranslator<?> translator) {
|
||||
sqlAppender.appendSql( "json_query(json_array(" );
|
||||
arg.accept( translator );
|
||||
sqlAppender.appendSql( " null on null),'$.*')" );
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
* 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.type.spi.TypeConfiguration;
|
||||
|
||||
/**
|
||||
* H2 json_arrayagg function.
|
||||
*/
|
||||
public class H2JsonArrayAggFunction extends JsonArrayAggFunction {
|
||||
|
||||
public H2JsonArrayAggFunction(TypeConfiguration typeConfiguration) {
|
||||
super( true, typeConfiguration );
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void renderReturningClause(SqlAppender sqlAppender, Expression arg, SqlAstTranslator<?> translator) {
|
||||
// No returning clause supported or needed
|
||||
}
|
||||
}
|
|
@ -0,0 +1,109 @@
|
|||
/*
|
||||
* 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.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.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.predicate.Predicate;
|
||||
import org.hibernate.sql.ast.tree.select.SortSpecification;
|
||||
import org.hibernate.type.spi.TypeConfiguration;
|
||||
|
||||
/**
|
||||
* SQL Server json_arrayagg function.
|
||||
*/
|
||||
public class HANAJsonArrayAggFunction extends JsonArrayAggFunction {
|
||||
|
||||
public HANAJsonArrayAggFunction(TypeConfiguration typeConfiguration) {
|
||||
super( false, typeConfiguration );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(
|
||||
SqlAppender sqlAppender,
|
||||
List<? extends SqlAstNode> sqlAstArguments,
|
||||
Predicate filter,
|
||||
List<SortSpecification> withinGroup,
|
||||
ReturnableType<?> returnType,
|
||||
SqlAstTranslator<?> translator) {
|
||||
final boolean caseWrapper = filter != null;
|
||||
sqlAppender.appendSql( "'['||string_agg(" );
|
||||
final JsonNullBehavior nullBehavior;
|
||||
if ( sqlAstArguments.size() > 1 ) {
|
||||
nullBehavior = (JsonNullBehavior) sqlAstArguments.get( 1 );
|
||||
}
|
||||
else {
|
||||
nullBehavior = JsonNullBehavior.ABSENT;
|
||||
}
|
||||
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." );
|
||||
}
|
||||
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 );
|
||||
}
|
||||
sqlAppender.appendSql( ",','" );
|
||||
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();
|
||||
}
|
||||
sqlAppender.appendSql( ")||']'" );
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void renderArgument(
|
||||
SqlAppender sqlAppender,
|
||||
Expression arg,
|
||||
JsonNullBehavior nullBehavior,
|
||||
SqlAstTranslator<?> translator) {
|
||||
// Convert the value to JSON
|
||||
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 {
|
||||
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')" );
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
* 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.type.spi.TypeConfiguration;
|
||||
|
||||
/**
|
||||
* HSQLDB json_arrayagg function.
|
||||
*/
|
||||
public class HSQLJsonArrayAggFunction extends JsonArrayAggFunction {
|
||||
|
||||
public HSQLJsonArrayAggFunction(TypeConfiguration typeConfiguration) {
|
||||
super( false, typeConfiguration );
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void renderReturningClause(SqlAppender sqlAppender, Expression arg, SqlAstTranslator<?> translator) {
|
||||
// No returning clause needed
|
||||
}
|
||||
}
|
|
@ -0,0 +1,143 @@
|
|||
/*
|
||||
* 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.Collections;
|
||||
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.Distinct;
|
||||
import org.hibernate.sql.ast.tree.expression.Expression;
|
||||
import org.hibernate.sql.ast.tree.expression.JsonNullBehavior;
|
||||
import org.hibernate.sql.ast.tree.predicate.Predicate;
|
||||
import org.hibernate.sql.ast.tree.select.SortSpecification;
|
||||
import org.hibernate.type.SqlTypes;
|
||||
import org.hibernate.type.spi.TypeConfiguration;
|
||||
|
||||
/**
|
||||
* Standard json_arrayagg function.
|
||||
*/
|
||||
public class JsonArrayAggFunction extends AbstractSqmSelfRenderingFunctionDescriptor {
|
||||
|
||||
protected final boolean supportsFilter;
|
||||
|
||||
public JsonArrayAggFunction(boolean supportsFilter, TypeConfiguration typeConfiguration) {
|
||||
super(
|
||||
"json_arrayagg",
|
||||
FunctionKind.ORDERED_SET_AGGREGATE,
|
||||
StandardArgumentsValidators.between( 1, 2 ),
|
||||
StandardFunctionReturnTypeResolvers.invariant(
|
||||
typeConfiguration.getBasicTypeRegistry().resolve( String.class, SqlTypes.JSON_ARRAY )
|
||||
),
|
||||
null
|
||||
);
|
||||
this.supportsFilter = supportsFilter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(
|
||||
SqlAppender sqlAppender,
|
||||
List<? extends SqlAstNode> sqlAstArguments,
|
||||
ReturnableType<?> returnType,
|
||||
SqlAstTranslator<?> walker) {
|
||||
render( sqlAppender, sqlAstArguments, null, Collections.emptyList(), returnType, walker );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(
|
||||
SqlAppender sqlAppender,
|
||||
List<? extends SqlAstNode> sqlAstArguments,
|
||||
Predicate filter,
|
||||
List<SortSpecification> withinGroup,
|
||||
ReturnableType<?> returnType,
|
||||
SqlAstTranslator<?> translator) {
|
||||
final boolean caseWrapper = filter != null && !translator.supportsFilterClause();
|
||||
sqlAppender.appendSql( "json_arrayagg(" );
|
||||
final SqlAstNode firstArg = sqlAstArguments.get( 0 );
|
||||
final JsonNullBehavior nullBehavior;
|
||||
if ( sqlAstArguments.size() > 1 ) {
|
||||
nullBehavior = (JsonNullBehavior) sqlAstArguments.get( 1 );
|
||||
}
|
||||
else {
|
||||
nullBehavior = JsonNullBehavior.ABSENT;
|
||||
}
|
||||
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." );
|
||||
}
|
||||
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 );
|
||||
}
|
||||
translator.getCurrentClauseStack().pop();
|
||||
}
|
||||
if ( nullBehavior == JsonNullBehavior.NULL ) {
|
||||
sqlAppender.appendSql( " null on null" );
|
||||
}
|
||||
else {
|
||||
sqlAppender.appendSql( " absent on null" );
|
||||
}
|
||||
renderReturningClause( sqlAppender, arg, 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 renderReturningClause(SqlAppender sqlAppender, Expression arg, SqlAstTranslator<?> translator) {
|
||||
sqlAppender.appendSql( " returning " );
|
||||
sqlAppender.appendSql(
|
||||
translator.getSessionFactory().getTypeConfiguration().getDdlTypeRegistry()
|
||||
.getTypeName( SqlTypes.JSON, translator.getSessionFactory().getJdbcServices().getDialect() )
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,98 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
|
||||
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
|
||||
*/
|
||||
package org.hibernate.dialect.function.json;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.hibernate.QueryException;
|
||||
import org.hibernate.query.ReturnableType;
|
||||
import org.hibernate.sql.ast.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.predicate.Predicate;
|
||||
import org.hibernate.sql.ast.tree.select.SortSpecification;
|
||||
import org.hibernate.type.spi.TypeConfiguration;
|
||||
|
||||
/**
|
||||
* MariaDB json_arrayagg function.
|
||||
*/
|
||||
public class MariaDBJsonArrayAggFunction extends JsonArrayAggFunction {
|
||||
|
||||
public MariaDBJsonArrayAggFunction(TypeConfiguration typeConfiguration) {
|
||||
super( false, typeConfiguration );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(
|
||||
SqlAppender sqlAppender,
|
||||
List<? extends SqlAstNode> sqlAstArguments,
|
||||
Predicate filter,
|
||||
List<SortSpecification> withinGroup,
|
||||
ReturnableType<?> returnType,
|
||||
SqlAstTranslator<?> translator) {
|
||||
final boolean caseWrapper = filter != null;
|
||||
sqlAppender.appendSql( "concat('[',group_concat(" );
|
||||
final JsonNullBehavior nullBehavior;
|
||||
if ( sqlAstArguments.size() > 1 ) {
|
||||
nullBehavior = (JsonNullBehavior) sqlAstArguments.get( 1 );
|
||||
}
|
||||
else {
|
||||
nullBehavior = JsonNullBehavior.ABSENT;
|
||||
}
|
||||
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." );
|
||||
}
|
||||
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 );
|
||||
}
|
||||
translator.getCurrentClauseStack().pop();
|
||||
}
|
||||
sqlAppender.appendSql( " separator ','),']')" );
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void renderArgument(
|
||||
SqlAppender sqlAppender,
|
||||
Expression arg,
|
||||
JsonNullBehavior nullBehavior,
|
||||
SqlAstTranslator<?> translator) {
|
||||
// Convert SQL type to JSON type
|
||||
sqlAppender.appendSql( "json_extract(json_array(" );
|
||||
arg.accept( translator );
|
||||
sqlAppender.appendSql( "),'$[0]')" );
|
||||
}
|
||||
}
|
|
@ -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 org.hibernate.query.ReturnableType;
|
||||
import org.hibernate.sql.ast.SqlAstTranslator;
|
||||
import org.hibernate.sql.ast.spi.SqlAppender;
|
||||
import org.hibernate.sql.ast.tree.expression.JsonPathPassingClause;
|
||||
import org.hibernate.sql.ast.tree.expression.JsonQueryEmptyBehavior;
|
||||
import org.hibernate.sql.ast.tree.expression.JsonQueryErrorBehavior;
|
||||
import org.hibernate.sql.ast.tree.expression.JsonQueryWrapMode;
|
||||
import org.hibernate.type.spi.TypeConfiguration;
|
||||
|
||||
/**
|
||||
* MariaDB json_query function.
|
||||
*/
|
||||
public class MariaDBJsonQueryFunction extends JsonQueryFunction {
|
||||
|
||||
public MariaDBJsonQueryFunction(TypeConfiguration typeConfiguration) {
|
||||
super( typeConfiguration, true, false );
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void render(
|
||||
SqlAppender sqlAppender,
|
||||
JsonQueryArguments arguments,
|
||||
ReturnableType<?> returnType,
|
||||
SqlAstTranslator<?> walker) {
|
||||
// json_extract errors by default
|
||||
if ( arguments.errorBehavior() != null && arguments.errorBehavior() != JsonQueryErrorBehavior.ERROR
|
||||
|| arguments.emptyBehavior() == JsonQueryEmptyBehavior.ERROR
|
||||
// Can't emulate DEFAULT ON EMPTY since we can't differentiate between a NULL value and EMPTY
|
||||
|| arguments.emptyBehavior() != null && arguments.emptyBehavior() != JsonQueryEmptyBehavior.NULL ) {
|
||||
super.render( sqlAppender, arguments, returnType, walker );
|
||||
}
|
||||
else {
|
||||
final JsonQueryWrapMode wrapMode = arguments.wrapMode();
|
||||
final DecorationMode decorationMode = determineDecorationMode( arguments, walker, wrapMode );
|
||||
if ( decorationMode == DecorationMode.WRAP ) {
|
||||
sqlAppender.appendSql( "concat('['," );
|
||||
}
|
||||
else if ( decorationMode == DecorationMode.TRIM ) {
|
||||
sqlAppender.appendSql( "trim(leading '[' from trim(trailing ']' from " );
|
||||
}
|
||||
|
||||
sqlAppender.appendSql( "nullif(json_extract(" );
|
||||
arguments.jsonDocument().accept( walker );
|
||||
sqlAppender.appendSql( "," );
|
||||
final JsonPathPassingClause passingClause = arguments.passingClause();
|
||||
if ( passingClause == null ) {
|
||||
arguments.jsonPath().accept( walker );
|
||||
}
|
||||
else {
|
||||
JsonPathHelper.appendJsonPathConcatPassingClause(
|
||||
sqlAppender,
|
||||
arguments.jsonPath(),
|
||||
passingClause, walker
|
||||
);
|
||||
}
|
||||
sqlAppender.appendSql( "),'null')" );
|
||||
if ( decorationMode == DecorationMode.WRAP ) {
|
||||
sqlAppender.appendSql( ",']')" );
|
||||
}
|
||||
else if ( decorationMode == DecorationMode.TRIM ) {
|
||||
sqlAppender.appendSql( "))" );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum DecorationMode { NONE, WRAP, TRIM }
|
||||
|
||||
private static DecorationMode determineDecorationMode(
|
||||
JsonQueryArguments arguments,
|
||||
SqlAstTranslator<?> walker,
|
||||
JsonQueryWrapMode wrapMode) {
|
||||
if ( wrapMode == JsonQueryWrapMode.WITH_WRAPPER ) {
|
||||
final String jsonPath = walker.getLiteralValue( arguments.jsonPath() );
|
||||
if ( jsonPath.indexOf( '*' ) != -1 ) {
|
||||
// If the JSON path contains a star, MySQL will always wrap the result
|
||||
return DecorationMode.NONE;
|
||||
}
|
||||
else {
|
||||
// Otherwise we have to wrap the result manually
|
||||
return DecorationMode.WRAP;
|
||||
}
|
||||
}
|
||||
else if ( wrapMode == JsonQueryWrapMode.WITHOUT_WRAPPER ) {
|
||||
final String jsonPath = walker.getLiteralValue( arguments.jsonPath() );
|
||||
if ( jsonPath.indexOf( '*' ) != -1 ) {
|
||||
// If the JSON path contains a star, MySQL will always wrap the result,
|
||||
// so we have to trim the brackets
|
||||
return DecorationMode.TRIM;
|
||||
}
|
||||
else {
|
||||
// Nothing to do
|
||||
return DecorationMode.NONE;
|
||||
}
|
||||
}
|
||||
else {
|
||||
return DecorationMode.NONE;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,98 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
|
||||
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
|
||||
*/
|
||||
package org.hibernate.dialect.function.json;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.hibernate.QueryException;
|
||||
import org.hibernate.query.ReturnableType;
|
||||
import org.hibernate.sql.ast.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.predicate.Predicate;
|
||||
import org.hibernate.sql.ast.tree.select.SortSpecification;
|
||||
import org.hibernate.type.spi.TypeConfiguration;
|
||||
|
||||
/**
|
||||
* MySQL json_arrayagg function.
|
||||
*/
|
||||
public class MySQLJsonArrayAggFunction extends JsonArrayAggFunction {
|
||||
|
||||
public MySQLJsonArrayAggFunction(TypeConfiguration typeConfiguration) {
|
||||
super( false, typeConfiguration );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(
|
||||
SqlAppender sqlAppender,
|
||||
List<? extends SqlAstNode> sqlAstArguments,
|
||||
Predicate filter,
|
||||
List<SortSpecification> withinGroup,
|
||||
ReturnableType<?> returnType,
|
||||
SqlAstTranslator<?> translator) {
|
||||
final boolean caseWrapper = filter != null;
|
||||
sqlAppender.appendSql( "cast(concat('[',group_concat(" );
|
||||
final JsonNullBehavior nullBehavior;
|
||||
if ( sqlAstArguments.size() > 1 ) {
|
||||
nullBehavior = (JsonNullBehavior) sqlAstArguments.get( 1 );
|
||||
}
|
||||
else {
|
||||
nullBehavior = JsonNullBehavior.ABSENT;
|
||||
}
|
||||
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." );
|
||||
}
|
||||
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 );
|
||||
}
|
||||
translator.getCurrentClauseStack().pop();
|
||||
}
|
||||
sqlAppender.appendSql( " separator ','),']') as json)" );
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void renderArgument(
|
||||
SqlAppender sqlAppender,
|
||||
Expression arg,
|
||||
JsonNullBehavior nullBehavior,
|
||||
SqlAstTranslator<?> translator) {
|
||||
// Convert SQL type to JSON type
|
||||
sqlAppender.appendSql( "json_extract(json_array(" );
|
||||
arg.accept( translator );
|
||||
sqlAppender.appendSql( "),'$[0]')" );
|
||||
}
|
||||
}
|
|
@ -12,6 +12,7 @@ import org.hibernate.sql.ast.spi.SqlAppender;
|
|||
import org.hibernate.sql.ast.tree.expression.JsonPathPassingClause;
|
||||
import org.hibernate.sql.ast.tree.expression.JsonQueryEmptyBehavior;
|
||||
import org.hibernate.sql.ast.tree.expression.JsonQueryErrorBehavior;
|
||||
import org.hibernate.sql.ast.tree.expression.JsonQueryWrapMode;
|
||||
import org.hibernate.type.spi.TypeConfiguration;
|
||||
|
||||
/**
|
||||
|
@ -37,6 +38,15 @@ public class MySQLJsonQueryFunction extends JsonQueryFunction {
|
|||
super.render( sqlAppender, arguments, returnType, walker );
|
||||
}
|
||||
else {
|
||||
final JsonQueryWrapMode wrapMode = arguments.wrapMode();
|
||||
final DecorationMode decorationMode = determineDecorationMode( arguments, walker, wrapMode );
|
||||
if ( decorationMode == DecorationMode.WRAP ) {
|
||||
sqlAppender.appendSql( "concat('['," );
|
||||
}
|
||||
else if ( decorationMode == DecorationMode.TRIM ) {
|
||||
sqlAppender.appendSql( "cast(trim(leading '[' from trim(trailing ']' from " );
|
||||
}
|
||||
|
||||
sqlAppender.appendSql( "nullif(json_extract(" );
|
||||
arguments.jsonDocument().accept( walker );
|
||||
sqlAppender.appendSql( "," );
|
||||
|
@ -52,6 +62,46 @@ public class MySQLJsonQueryFunction extends JsonQueryFunction {
|
|||
);
|
||||
}
|
||||
sqlAppender.appendSql( "),cast('null' as json))" );
|
||||
if ( decorationMode == DecorationMode.WRAP ) {
|
||||
sqlAppender.appendSql( ",']')" );
|
||||
}
|
||||
else if ( decorationMode == DecorationMode.TRIM ) {
|
||||
sqlAppender.appendSql( ")) as json)" );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum DecorationMode { NONE, WRAP, TRIM }
|
||||
|
||||
private static DecorationMode determineDecorationMode(
|
||||
JsonQueryArguments arguments,
|
||||
SqlAstTranslator<?> walker,
|
||||
JsonQueryWrapMode wrapMode) {
|
||||
if ( wrapMode == JsonQueryWrapMode.WITH_WRAPPER ) {
|
||||
final String jsonPath = walker.getLiteralValue( arguments.jsonPath() );
|
||||
if ( jsonPath.indexOf( '*' ) != -1 ) {
|
||||
// If the JSON path contains a star, MySQL will always wrap the result
|
||||
return DecorationMode.NONE;
|
||||
}
|
||||
else {
|
||||
// Otherwise we have to wrap the result manually
|
||||
return DecorationMode.WRAP;
|
||||
}
|
||||
}
|
||||
else if ( wrapMode == JsonQueryWrapMode.WITHOUT_WRAPPER ) {
|
||||
final String jsonPath = walker.getLiteralValue( arguments.jsonPath() );
|
||||
if ( jsonPath.indexOf( '*' ) != -1 ) {
|
||||
// If the JSON path contains a star, MySQL will always wrap the result,
|
||||
// so we have to trim the brackets
|
||||
return DecorationMode.TRIM;
|
||||
}
|
||||
else {
|
||||
// Nothing to do
|
||||
return DecorationMode.NONE;
|
||||
}
|
||||
}
|
||||
else {
|
||||
return DecorationMode.NONE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* 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.metamodel.mapping.JdbcMappingContainer;
|
||||
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.SqlTypes;
|
||||
import org.hibernate.type.spi.TypeConfiguration;
|
||||
|
||||
/**
|
||||
* Oracle json_arrayagg function.
|
||||
*/
|
||||
public class OracleJsonArrayAggFunction extends JsonArrayAggFunction {
|
||||
|
||||
public OracleJsonArrayAggFunction(TypeConfiguration typeConfiguration) {
|
||||
super( false, typeConfiguration );
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void renderArgument(
|
||||
SqlAppender sqlAppender,
|
||||
Expression arg,
|
||||
JsonNullBehavior nullBehavior,
|
||||
SqlAstTranslator<?> translator) {
|
||||
arg.accept( translator );
|
||||
final JdbcMappingContainer expressionType = arg.getExpressionType();
|
||||
if ( expressionType != null && expressionType.getSingleJdbcMapping().getJdbcType().isJson()
|
||||
&& !SqlTypes.isJsonType( expressionType.getSingleJdbcMapping().getJdbcType().getDdlTypeCode() ) ) {
|
||||
sqlAppender.appendSql( " format json" );
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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.dialect.function.json;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
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.predicate.Predicate;
|
||||
import org.hibernate.sql.ast.tree.select.SortSpecification;
|
||||
import org.hibernate.type.SqlTypes;
|
||||
import org.hibernate.type.spi.TypeConfiguration;
|
||||
|
||||
/**
|
||||
* PostgreSQL json_arrayagg function.
|
||||
*/
|
||||
public class PostgreSQLJsonArrayAggFunction extends JsonArrayAggFunction {
|
||||
|
||||
public PostgreSQLJsonArrayAggFunction(TypeConfiguration typeConfiguration) {
|
||||
super( true, typeConfiguration );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(
|
||||
SqlAppender sqlAppender,
|
||||
List<? extends SqlAstNode> sqlAstArguments,
|
||||
Predicate filter,
|
||||
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 );
|
||||
}
|
||||
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." );
|
||||
}
|
||||
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 );
|
||||
}
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -18,6 +18,7 @@ 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.sql.ast.tree.expression.Literal;
|
||||
import org.hibernate.type.spi.TypeConfiguration;
|
||||
|
||||
|
@ -36,14 +37,24 @@ public class PostgreSQLJsonQueryFunction extends JsonQueryFunction {
|
|||
JsonQueryArguments arguments,
|
||||
ReturnableType<?> returnType,
|
||||
SqlAstTranslator<?> walker) {
|
||||
// jsonb_path_query_first errors by default
|
||||
// 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" );
|
||||
}
|
||||
sqlAppender.appendSql( "jsonb_path_query_array(" );
|
||||
final JsonQueryWrapMode wrapMode = arguments.wrapMode();
|
||||
|
||||
if ( wrapMode == JsonQueryWrapMode.WITH_WRAPPER ) {
|
||||
sqlAppender.appendSql( "jsonb_path_query_array(" );
|
||||
}
|
||||
else if ( wrapMode == JsonQueryWrapMode.WITH_CONDITIONAL_WRAPPER ) {
|
||||
sqlAppender.appendSql( "(select case when count(*) over () > 1 then jsonb_agg(t.v) else percentile_disc(0) within group (order by t.v) end from jsonb_path_query(" );
|
||||
}
|
||||
else {
|
||||
sqlAppender.appendSql( "(select t.v from jsonb_path_query(" );
|
||||
}
|
||||
final boolean needsCast = !arguments.isJsonType() && arguments.jsonDocument() instanceof JdbcParameter;
|
||||
if ( needsCast ) {
|
||||
sqlAppender.appendSql( "cast(" );
|
||||
|
@ -75,7 +86,12 @@ public class PostgreSQLJsonQueryFunction extends JsonQueryFunction {
|
|||
}
|
||||
sqlAppender.append( ')' );
|
||||
}
|
||||
// Unquote the value
|
||||
sqlAppender.appendSql( ")#>>'{}'" );
|
||||
|
||||
if ( wrapMode != JsonQueryWrapMode.WITH_WRAPPER ) {
|
||||
sqlAppender.appendSql( ") t(v))" );
|
||||
}
|
||||
else {
|
||||
sqlAppender.appendSql( ')' );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,101 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
|
||||
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
|
||||
*/
|
||||
package org.hibernate.dialect.function.json;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
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.predicate.Predicate;
|
||||
import org.hibernate.sql.ast.tree.select.SortSpecification;
|
||||
import org.hibernate.type.spi.TypeConfiguration;
|
||||
|
||||
/**
|
||||
* SQL Server json_arrayagg function.
|
||||
*/
|
||||
public class SQLServerJsonArrayAggFunction extends JsonArrayAggFunction {
|
||||
|
||||
public SQLServerJsonArrayAggFunction(TypeConfiguration typeConfiguration) {
|
||||
super( false, typeConfiguration );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(
|
||||
SqlAppender sqlAppender,
|
||||
List<? extends SqlAstNode> sqlAstArguments,
|
||||
Predicate filter,
|
||||
List<SortSpecification> withinGroup,
|
||||
ReturnableType<?> returnType,
|
||||
SqlAstTranslator<?> translator) {
|
||||
final boolean caseWrapper = filter != null;
|
||||
sqlAppender.appendSql( "'['+string_agg(" );
|
||||
final JsonNullBehavior nullBehavior;
|
||||
if ( sqlAstArguments.size() > 1 ) {
|
||||
nullBehavior = (JsonNullBehavior) sqlAstArguments.get( 1 );
|
||||
}
|
||||
else {
|
||||
nullBehavior = JsonNullBehavior.ABSENT;
|
||||
}
|
||||
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." );
|
||||
}
|
||||
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 );
|
||||
}
|
||||
sqlAppender.appendSql( ",',')" );
|
||||
if ( withinGroup != null && !withinGroup.isEmpty() ) {
|
||||
translator.getCurrentClauseStack().push( Clause.WITHIN_GROUP );
|
||||
sqlAppender.appendSql( " within group (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();
|
||||
sqlAppender.appendSql( ')' );
|
||||
}
|
||||
sqlAppender.appendSql( "+']'" );
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void renderArgument(
|
||||
SqlAppender sqlAppender,
|
||||
Expression arg,
|
||||
JsonNullBehavior nullBehavior,
|
||||
SqlAstTranslator<?> translator) {
|
||||
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)" );
|
||||
}
|
||||
}
|
|
@ -15,10 +15,12 @@ import java.util.function.Supplier;
|
|||
import org.hibernate.Incubating;
|
||||
import org.hibernate.boot.registry.StandardServiceRegistry;
|
||||
import org.hibernate.cfg.AvailableSettings;
|
||||
import org.hibernate.dialect.Dialect;
|
||||
import org.hibernate.engine.config.spi.ConfigurationService;
|
||||
import org.hibernate.engine.jdbc.spi.JdbcServices;
|
||||
import org.hibernate.internal.util.StringHelper;
|
||||
import org.hibernate.internal.util.collections.ArrayHelper;
|
||||
import org.hibernate.service.ServiceRegistry;
|
||||
import org.hibernate.type.SqlTypes;
|
||||
import org.hibernate.type.descriptor.JdbcTypeNameMapper;
|
||||
|
||||
|
@ -509,6 +511,21 @@ public final class ConfigurationHelper {
|
|||
.getPreferredSqlTypeCodeForBoolean();
|
||||
}
|
||||
|
||||
@Incubating
|
||||
public static synchronized int getPreferredSqlTypeCodeForBoolean(ServiceRegistry serviceRegistry, Dialect dialect) {
|
||||
final Integer typeCode = serviceRegistry.requireService( ConfigurationService.class ).getSetting(
|
||||
AvailableSettings.PREFERRED_BOOLEAN_JDBC_TYPE,
|
||||
TypeCodeConverter.INSTANCE
|
||||
);
|
||||
if ( typeCode != null ) {
|
||||
INCUBATION_LOGGER.incubatingSetting( AvailableSettings.PREFERRED_BOOLEAN_JDBC_TYPE );
|
||||
return typeCode;
|
||||
}
|
||||
|
||||
// default to the Dialect answer
|
||||
return dialect.getPreferredSqlTypeCodeForBoolean();
|
||||
}
|
||||
|
||||
@Incubating
|
||||
public static synchronized int getPreferredSqlTypeCodeForDuration(StandardServiceRegistry serviceRegistry) {
|
||||
final Integer explicitSetting = serviceRegistry.requireService( ConfigurationService.class ).getSetting(
|
||||
|
|
|
@ -3775,6 +3775,78 @@ public interface HibernateCriteriaBuilder extends CriteriaBuilder {
|
|||
@Incubating
|
||||
JpaExpression<String> jsonArrayWithNulls(Expression<?>... values);
|
||||
|
||||
/**
|
||||
* Aggregates the given value into a JSON array.
|
||||
*
|
||||
* @since 7.0
|
||||
*/
|
||||
@Incubating
|
||||
JpaExpression<String> jsonArrayAgg(Expression<?> value);
|
||||
|
||||
/**
|
||||
* Aggregates the given value into a JSON array.
|
||||
* Ordering values based on the given order by items.
|
||||
*
|
||||
* @since 7.0
|
||||
*/
|
||||
@Incubating
|
||||
JpaExpression<String> jsonArrayAgg(Expression<?> value, JpaOrder... orderBy);
|
||||
|
||||
/**
|
||||
* Aggregates the given value into a JSON array.
|
||||
* Filtering rows that don't match the given filter predicate.
|
||||
*
|
||||
* @since 7.0
|
||||
*/
|
||||
@Incubating
|
||||
JpaExpression<String> jsonArrayAgg(Expression<?> value, Predicate filter);
|
||||
|
||||
/**
|
||||
* Aggregates the given value into a JSON array.
|
||||
* Filtering rows that don't match the given filter predicate.
|
||||
* Ordering values based on the given order by items.
|
||||
*
|
||||
* @since 7.0
|
||||
*/
|
||||
@Incubating
|
||||
JpaExpression<String> jsonArrayAgg(Expression<?> value, Predicate filter, JpaOrder... orderBy);
|
||||
|
||||
/**
|
||||
* Aggregates the given value into a JSON array, retaining {@code null} values in the JSON array.
|
||||
*
|
||||
* @since 7.0
|
||||
*/
|
||||
@Incubating
|
||||
JpaExpression<String> jsonArrayAggWithNulls(Expression<?> value);
|
||||
|
||||
/**
|
||||
* Aggregates the given value into a JSON array, retaining {@code null} values in the JSON array.
|
||||
* Ordering values based on the given order by items.
|
||||
*
|
||||
* @since 7.0
|
||||
*/
|
||||
@Incubating
|
||||
JpaExpression<String> jsonArrayAggWithNulls(Expression<?> value, JpaOrder... orderBy);
|
||||
|
||||
/**
|
||||
* Aggregates the given value into a JSON array, retaining {@code null} values in the JSON array.
|
||||
* Filtering rows that don't match the given filter predicate.
|
||||
*
|
||||
* @since 7.0
|
||||
*/
|
||||
@Incubating
|
||||
JpaExpression<String> jsonArrayAggWithNulls(Expression<?> value, Predicate filter);
|
||||
|
||||
/**
|
||||
* Aggregates the given value into a JSON array, retaining {@code null} values in the JSON array.
|
||||
* Filtering rows that don't match the given filter predicate.
|
||||
* Ordering values based on the given order by items.
|
||||
*
|
||||
* @since 7.0
|
||||
*/
|
||||
@Incubating
|
||||
JpaExpression<String> jsonArrayAggWithNulls(Expression<?> value, Predicate filter, JpaOrder... orderBy);
|
||||
|
||||
@Override
|
||||
JpaPredicate and(List<Predicate> restrictions);
|
||||
|
||||
|
|
|
@ -3424,4 +3424,52 @@ public class HibernateCriteriaBuilderDelegate implements HibernateCriteriaBuilde
|
|||
public JpaExpression<String> jsonArrayWithNulls(Expression<?>... values) {
|
||||
return criteriaBuilder.jsonArrayWithNulls( values );
|
||||
}
|
||||
|
||||
@Override
|
||||
@Incubating
|
||||
public JpaExpression<String> jsonArrayAgg(Expression<?> value) {
|
||||
return criteriaBuilder.jsonArrayAgg( value );
|
||||
}
|
||||
|
||||
@Override
|
||||
@Incubating
|
||||
public JpaExpression<String> jsonArrayAggWithNulls(Expression<?> value) {
|
||||
return criteriaBuilder.jsonArrayAggWithNulls( value );
|
||||
}
|
||||
|
||||
@Override
|
||||
@Incubating
|
||||
public JpaExpression<String> jsonArrayAgg(Expression<?> value, JpaOrder... orderBy) {
|
||||
return criteriaBuilder.jsonArrayAgg( value, orderBy );
|
||||
}
|
||||
|
||||
@Override
|
||||
@Incubating
|
||||
public JpaExpression<String> jsonArrayAgg(Expression<?> value, Predicate filter) {
|
||||
return criteriaBuilder.jsonArrayAgg( value, filter );
|
||||
}
|
||||
|
||||
@Override
|
||||
@Incubating
|
||||
public JpaExpression<String> jsonArrayAgg(Expression<?> value, Predicate filter, JpaOrder... orderBy) {
|
||||
return criteriaBuilder.jsonArrayAgg( value, filter, orderBy );
|
||||
}
|
||||
|
||||
@Override
|
||||
@Incubating
|
||||
public JpaExpression<String> jsonArrayAggWithNulls(Expression<?> value, JpaOrder... orderBy) {
|
||||
return criteriaBuilder.jsonArrayAggWithNulls( value, orderBy );
|
||||
}
|
||||
|
||||
@Override
|
||||
@Incubating
|
||||
public JpaExpression<String> jsonArrayAggWithNulls(Expression<?> value, Predicate filter) {
|
||||
return criteriaBuilder.jsonArrayAggWithNulls( value, filter );
|
||||
}
|
||||
|
||||
@Override
|
||||
@Incubating
|
||||
public JpaExpression<String> jsonArrayAggWithNulls(Expression<?> value, Predicate filter, JpaOrder... orderBy) {
|
||||
return criteriaBuilder.jsonArrayAggWithNulls( value, filter, orderBy );
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2907,6 +2907,30 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
|
|||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object visitJsonArrayAggFunction(HqlParser.JsonArrayAggFunctionContext ctx) {
|
||||
final HqlParser.JsonNullClauseContext jsonNullClauseContext = ctx.jsonNullClause();
|
||||
final ArrayList<SqmTypedNode<?>> arguments = new ArrayList<>( jsonNullClauseContext == null ? 1 : 2 );
|
||||
arguments.add( (SqmTypedNode<?>) ctx.expressionOrPredicate().accept( this ) );
|
||||
if ( jsonNullClauseContext != null ) {
|
||||
final TerminalNode firstToken = (TerminalNode) jsonNullClauseContext.getChild( 0 );
|
||||
arguments.add(
|
||||
firstToken.getSymbol().getType() == HqlParser.ABSENT
|
||||
? SqmJsonNullBehavior.ABSENT
|
||||
: SqmJsonNullBehavior.NULL
|
||||
);
|
||||
}
|
||||
return getFunctionDescriptor( "json_arrayagg" ).generateOrderedSetAggregateSqmExpression(
|
||||
arguments,
|
||||
getFilterExpression( ctx ),
|
||||
ctx.orderByClause() == null
|
||||
? null
|
||||
: visitOrderByClause( ctx.orderByClause(), false ),
|
||||
null,
|
||||
creationContext.getQueryEngine()
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SqmPredicate visitIncludesPredicate(HqlParser.IncludesPredicateContext ctx) {
|
||||
final boolean negated = ctx.NOT() != null;
|
||||
|
|
|
@ -655,6 +655,30 @@ public interface NodeBuilder extends HibernateCriteriaBuilder, BindingContext {
|
|||
@Override
|
||||
SqmExpression<String> jsonObject(Map<?, ? extends Expression<?>> keyValues);
|
||||
|
||||
@Override
|
||||
SqmExpression<String> jsonArrayAgg(Expression<?> value);
|
||||
|
||||
@Override
|
||||
SqmExpression<String> jsonArrayAggWithNulls(Expression<?> value);
|
||||
|
||||
@Override
|
||||
SqmExpression<String> jsonArrayAggWithNulls(Expression<?> value, Predicate filter, JpaOrder... orderBy);
|
||||
|
||||
@Override
|
||||
SqmExpression<String> jsonArrayAggWithNulls(Expression<?> value, Predicate filter);
|
||||
|
||||
@Override
|
||||
SqmExpression<String> jsonArrayAggWithNulls(Expression<?> value, JpaOrder... orderBy);
|
||||
|
||||
@Override
|
||||
SqmExpression<String> jsonArrayAgg(Expression<?> value, Predicate filter, JpaOrder... orderBy);
|
||||
|
||||
@Override
|
||||
SqmExpression<String> jsonArrayAgg(Expression<?> value, Predicate filter);
|
||||
|
||||
@Override
|
||||
SqmExpression<String> jsonArrayAgg(Expression<?> value, JpaOrder... orderBy);
|
||||
|
||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// Covariant overrides
|
||||
|
||||
|
|
|
@ -190,6 +190,7 @@ import jakarta.persistence.criteria.SetJoin;
|
|||
import jakarta.persistence.criteria.Subquery;
|
||||
import jakarta.persistence.criteria.TemporalField;
|
||||
import jakarta.persistence.metamodel.Bindable;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
import static org.hibernate.query.internal.QueryHelper.highestPrecedenceType;
|
||||
|
@ -5387,6 +5388,78 @@ public class SqmCriteriaNodeBuilder implements NodeBuilder, Serializable {
|
|||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SqmExpression<String> jsonArrayAgg(Expression<?> value) {
|
||||
return jsonArrayAgg( (SqmExpression<?>) value, null, null, null );
|
||||
}
|
||||
|
||||
@Override
|
||||
public SqmExpression<String> jsonArrayAgg(Expression<?> value, Predicate filter, JpaOrder... orderBy) {
|
||||
return jsonArrayAgg( (SqmExpression<?>) value, null, (SqmPredicate) filter, orderByClause( orderBy ) );
|
||||
}
|
||||
|
||||
@Override
|
||||
public SqmExpression<String> jsonArrayAgg(Expression<?> value, Predicate filter) {
|
||||
return jsonArrayAgg( (SqmExpression<?>) value, null, (SqmPredicate) filter, null );
|
||||
}
|
||||
|
||||
@Override
|
||||
public SqmExpression<String> jsonArrayAgg(Expression<?> value, JpaOrder... orderBy) {
|
||||
return jsonArrayAgg( (SqmExpression<?>) value, null, null, orderByClause( orderBy ) );
|
||||
}
|
||||
|
||||
@Override
|
||||
public SqmExpression<String> jsonArrayAggWithNulls(Expression<?> value) {
|
||||
return jsonArrayAgg( (SqmExpression<?>) value, SqmJsonNullBehavior.NULL, null, null );
|
||||
}
|
||||
|
||||
@Override
|
||||
public SqmExpression<String> jsonArrayAggWithNulls(Expression<?> value, Predicate filter, JpaOrder... orderBy) {
|
||||
return jsonArrayAgg(
|
||||
(SqmExpression<?>) value,
|
||||
SqmJsonNullBehavior.NULL,
|
||||
(SqmPredicate) filter,
|
||||
orderByClause( orderBy )
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SqmExpression<String> jsonArrayAggWithNulls(Expression<?> value, Predicate filter) {
|
||||
return jsonArrayAgg( (SqmExpression<?>) value, SqmJsonNullBehavior.NULL, (SqmPredicate) filter, null );
|
||||
}
|
||||
|
||||
@Override
|
||||
public SqmExpression<String> jsonArrayAggWithNulls(Expression<?> value, JpaOrder... orderBy) {
|
||||
return jsonArrayAgg( (SqmExpression<?>) value, SqmJsonNullBehavior.NULL, null, orderByClause( orderBy ) );
|
||||
}
|
||||
|
||||
private @Nullable SqmOrderByClause orderByClause(JpaOrder[] orderBy) {
|
||||
if ( orderBy.length == 0 ) {
|
||||
return null;
|
||||
}
|
||||
final SqmOrderByClause sqmOrderByClause = new SqmOrderByClause( orderBy.length );
|
||||
for ( JpaOrder jpaOrder : orderBy ) {
|
||||
sqmOrderByClause.addSortSpecification( (SqmSortSpecification) jpaOrder );
|
||||
}
|
||||
return sqmOrderByClause;
|
||||
}
|
||||
|
||||
private SqmExpression<String> jsonArrayAgg(
|
||||
SqmExpression<?> value,
|
||||
@Nullable SqmJsonNullBehavior nullBehavior,
|
||||
@Nullable SqmPredicate filterPredicate,
|
||||
@Nullable SqmOrderByClause orderByClause) {
|
||||
return getFunctionDescriptor( "json_arrayagg" ).generateOrderedSetAggregateSqmExpression(
|
||||
nullBehavior == null
|
||||
? Collections.singletonList( value )
|
||||
: Arrays.asList( value, SqmJsonNullBehavior.NULL ),
|
||||
filterPredicate,
|
||||
orderByClause,
|
||||
null,
|
||||
queryEngine
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SqmExpression<String> jsonObjectWithNulls(Map<?, ? extends Expression<?>> keyValues) {
|
||||
final var arguments = keyValuesAsAlternatingList( keyValues );
|
||||
|
|
|
@ -249,10 +249,8 @@ public class ArgumentTypesValidator implements ArgumentsValidator {
|
|||
case DATE -> jdbcType.hasDatePart();
|
||||
case TIME -> jdbcType.hasTimePart();
|
||||
case SPATIAL -> jdbcType.isSpatial();
|
||||
case JSON:
|
||||
return jdbcType.isJson();
|
||||
case IMPLICIT_JSON:
|
||||
return jdbcType.isImplicitJson();
|
||||
case JSON -> jdbcType.isJson();
|
||||
case IMPLICIT_JSON -> jdbcType.isImplicitJson();
|
||||
default -> true; // TODO: should we throw here?
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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.orm.test.function.json;
|
||||
|
||||
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.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
* @author Christian Beikov
|
||||
*/
|
||||
@DomainModel(standardModels = StandardDomainModel.GAMBIT)
|
||||
@SessionFactory
|
||||
@RequiresDialectFeature( feature = DialectFeatureChecks.SupportsJsonArrayAgg.class)
|
||||
public class JsonArrayAggregateTest {
|
||||
|
||||
@Test
|
||||
public void testSimple(SessionFactoryScope scope) {
|
||||
scope.inSession( em -> {
|
||||
//tag::hql-json-arrayagg-example[]
|
||||
em.createQuery( "select json_arrayagg(e.theString) from EntityOfBasics e" ).getResultList();
|
||||
//end::hql-json-arrayagg-example[]
|
||||
} );
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNull(SessionFactoryScope scope) {
|
||||
scope.inSession( em -> {
|
||||
//tag::hql-json-arrayagg-null-example[]
|
||||
em.createQuery( "select json_arrayagg(e.theString null on null) from EntityOfBasics e" ).getResultList();
|
||||
//end::hql-json-arrayagg-null-example[]
|
||||
} );
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOrderBy(SessionFactoryScope scope) {
|
||||
scope.inSession( em -> {
|
||||
//tag::hql-json-arrayagg-order-by-example[]
|
||||
em.createQuery( "select json_arrayagg(e.theString order by e.id) from EntityOfBasics e" ).getResultList();
|
||||
//end::hql-json-arrayagg-order-by-example[]
|
||||
} );
|
||||
}
|
||||
|
||||
}
|
|
@ -12,6 +12,7 @@ import java.util.List;
|
|||
import org.hibernate.HibernateException;
|
||||
import org.hibernate.JDBCException;
|
||||
import org.hibernate.dialect.MariaDBDialect;
|
||||
import org.hibernate.dialect.OracleDialect;
|
||||
import org.hibernate.sql.exec.ExecutionException;
|
||||
|
||||
import org.hibernate.testing.orm.junit.DialectFeatureChecks;
|
||||
|
@ -70,6 +71,7 @@ public class JsonExistsTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
@SkipForDialect(dialectClass = OracleDialect.class, majorVersion = 21, matchSubTypes = true, reason = "Oracle bug in versions before 23")
|
||||
public void testPassing(SessionFactoryScope scope) {
|
||||
scope.inSession( em -> {
|
||||
//tag::hql-json-exists-passing-example[]
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
package org.hibernate.orm.test.query.hql;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
@ -17,6 +18,7 @@ import org.hibernate.dialect.HSQLDialect;
|
|||
import org.hibernate.dialect.OracleDialect;
|
||||
import org.hibernate.type.SqlTypes;
|
||||
|
||||
import org.hibernate.testing.orm.domain.gambit.EntityOfBasics;
|
||||
import org.hibernate.testing.orm.junit.DialectContext;
|
||||
import org.hibernate.testing.orm.junit.DialectFeatureChecks;
|
||||
import org.hibernate.testing.orm.junit.DomainModel;
|
||||
|
@ -50,7 +52,10 @@ import static org.junit.jupiter.api.Assertions.assertInstanceOf;
|
|||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
@DomainModel( annotatedClasses = JsonFunctionTests.JsonHolder.class)
|
||||
@DomainModel( annotatedClasses = {
|
||||
JsonFunctionTests.JsonHolder.class,
|
||||
EntityOfBasics.class
|
||||
})
|
||||
@SessionFactory
|
||||
@Jira("https://hibernate.atlassian.net/browse/HHH-18496")
|
||||
public class JsonFunctionTests {
|
||||
|
@ -80,6 +85,16 @@ public class JsonFunctionTests {
|
|||
)
|
||||
);
|
||||
em.persist(entity);
|
||||
|
||||
EntityOfBasics e1 = new EntityOfBasics();
|
||||
e1.setId( 1 );
|
||||
e1.setTheString( "Dog" );
|
||||
EntityOfBasics e2 = new EntityOfBasics();
|
||||
e2.setId( 2 );
|
||||
e2.setTheString( "Cat" );
|
||||
|
||||
em.persist( e1 );
|
||||
em.persist( e2 );
|
||||
}
|
||||
);
|
||||
}
|
||||
|
@ -87,7 +102,10 @@ public class JsonFunctionTests {
|
|||
@AfterEach
|
||||
public void cleanupData(SessionFactoryScope scope) {
|
||||
scope.inTransaction(
|
||||
em -> em.createMutationQuery( "delete from JsonHolder" ).executeUpdate()
|
||||
em -> {
|
||||
em.createMutationQuery( "delete from EntityOfBasics" ).executeUpdate();
|
||||
em.createMutationQuery( "delete from JsonHolder" ).executeUpdate();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -316,6 +334,40 @@ public class JsonFunctionTests {
|
|||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsJsonArrayAgg.class)
|
||||
public void testJsonArrayAgg(SessionFactoryScope scope) {
|
||||
scope.inTransaction(
|
||||
session -> {
|
||||
String jsonArray = session.createQuery(
|
||||
"select json_arrayagg(e.theString) " +
|
||||
"from EntityOfBasics e",
|
||||
String.class
|
||||
).getSingleResult();
|
||||
Object[] array = parseArray( jsonArray );
|
||||
assertEquals( 2, array.length );
|
||||
assertTrue( Arrays.asList( array ).contains( "Cat" ) );
|
||||
assertTrue( Arrays.asList( array ).contains( "Dog" ) );
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsJsonArrayAgg.class)
|
||||
public void testJsonArrayAggOrderBy(SessionFactoryScope scope) {
|
||||
scope.inTransaction(
|
||||
session -> {
|
||||
String jsonArray = session.createQuery(
|
||||
"select json_arrayagg(e.theString order by e.theString)" +
|
||||
"from EntityOfBasics e",
|
||||
String.class
|
||||
).getSingleResult();
|
||||
Object[] array = parseArray( jsonArray );
|
||||
assertArrayEquals( new Object[]{ "Cat", "Dog" }, array );
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private static final ObjectMapper MAPPER = new ObjectMapper();
|
||||
|
||||
private static Map<String, Object> parseObject(String json) {
|
||||
|
|
|
@ -779,6 +779,12 @@ abstract public class DialectFeatureChecks {
|
|||
}
|
||||
}
|
||||
|
||||
public static class SupportsJsonArrayAgg implements DialectFeatureCheck {
|
||||
public boolean apply(Dialect dialect) {
|
||||
return definesFunction( dialect, "json_arrayagg" );
|
||||
}
|
||||
}
|
||||
|
||||
public static class IsJtds implements DialectFeatureCheck {
|
||||
public boolean apply(Dialect dialect) {
|
||||
return dialect instanceof SybaseDialect && ( (SybaseDialect) dialect ).getDriverKind() == SybaseDriverKind.JTDS;
|
||||
|
@ -1534,7 +1540,7 @@ abstract public class DialectFeatureChecks {
|
|||
}
|
||||
|
||||
@Override
|
||||
public NamedObjectRepository buildNamedQueryRepository(SessionFactoryImplementor sessionFactory) {
|
||||
public NamedObjectRepository buildNamedQueryRepository() {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue