HHH-18496 Add json_arrayagg

This commit is contained in:
Christian Beikov 2024-09-10 19:30:58 +02:00
parent 6b4cc28f0e
commit c58485c4ef
46 changed files with 1573 additions and 16 deletions

View File

@ -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

View File

@ -0,0 +1,5 @@
"json_arrayagg(" expressionOrPredicate jsonNullClause? orderByClause? ")" filterClause?
jsonNullClause
: ("absent"|"null") "on null"
;

View File

@ -436,6 +436,7 @@ public class DB2LegacyDialect extends Dialect {
functionFactory.jsonExists_no_passing();
functionFactory.jsonObject_db2();
functionFactory.jsonArray_db2();
functionFactory.jsonArrayAgg_db2();
}
}
}

View File

@ -406,6 +406,7 @@ public class H2LegacyDialect extends Dialect {
functionFactory.jsonValue_h2();
functionFactory.jsonQuery_h2();
functionFactory.jsonExists_h2();
functionFactory.jsonArrayAgg_h2();
}
}
else {

View File

@ -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

View File

@ -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 ()" )

View File

@ -658,6 +658,7 @@ public class MySQLLegacyDialect extends Dialect {
functionFactory.jsonExists_mysql();
functionFactory.jsonObject_mysql();
functionFactory.jsonArray_mysql();
functionFactory.jsonArrayAgg_mysql();
}
}

View File

@ -327,6 +327,7 @@ public class OracleLegacyDialect extends Dialect {
functionFactory.jsonExists_oracle();
functionFactory.jsonObject_oracle();
functionFactory.jsonArray_oracle();
functionFactory.jsonArrayAgg_oracle();
}
}

View File

@ -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();
}
}

View File

@ -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();

View File

@ -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];

View File

@ -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

View File

@ -422,6 +422,7 @@ public class DB2Dialect extends Dialect {
functionFactory.jsonExists_no_passing();
functionFactory.jsonObject_db2();
functionFactory.jsonArray_db2();
functionFactory.jsonArrayAgg_db2();
}
}

View File

@ -349,6 +349,7 @@ public class H2Dialect extends Dialect {
functionFactory.jsonValue_h2();
functionFactory.jsonQuery_h2();
functionFactory.jsonExists_h2();
functionFactory.jsonArrayAgg_h2();
}
}

View File

@ -498,6 +498,7 @@ public class HANADialect extends Dialect {
// Introduced in 2.0 SPS 04
functionFactory.jsonObject_hana();
functionFactory.jsonArray_hana();
functionFactory.jsonArrayAgg_hana();
}
}
}

View File

@ -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

View File

@ -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 ) )

View File

@ -643,6 +643,7 @@ public class MySQLDialect extends Dialect {
functionFactory.jsonExists_mysql();
functionFactory.jsonObject_mysql();
functionFactory.jsonArray_mysql();
functionFactory.jsonArrayAgg_mysql();
}
@Override

View File

@ -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 );

View File

@ -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();
}
}

View File

@ -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();

View File

@ -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 ) );
}
}

View File

@ -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),'$.*')" );
}
}

View File

@ -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
}
}

View File

@ -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')" );
}
}
}

View File

@ -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
}
}

View File

@ -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() )
);
}
}

View File

@ -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]')" );
}
}

View File

@ -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;
}
}
}

View File

@ -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]')" );
}
}

View File

@ -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;
}
}
}

View File

@ -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" );
}
}
}

View File

@ -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();
}
}
}

View File

@ -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( ')' );
}
}
}

View File

@ -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)" );
}
}

View File

@ -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(

View File

@ -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);

View File

@ -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 );
}
}

View File

@ -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;

View File

@ -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

View File

@ -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 );

View File

@ -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?
};
}

View File

@ -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[]
} );
}
}

View File

@ -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[]

View File

@ -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) {

View File

@ -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;
}