diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/function/array/JsonArrayViaElementArgumentReturnTypeResolver.java b/hibernate-core/src/main/java/org/hibernate/dialect/function/array/JsonArrayViaElementArgumentReturnTypeResolver.java index 3aa6e0255078..da1b97d26276 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/function/array/JsonArrayViaElementArgumentReturnTypeResolver.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/function/array/JsonArrayViaElementArgumentReturnTypeResolver.java @@ -84,9 +84,7 @@ public static BasicType resolveJsonArrayType(DomainType elementType, T final Class arrayClass = Array.newInstance( elementType.getJavaType(), 0 ).getClass(); @SuppressWarnings("unchecked") final BasicPluralJavaType arrayJavaType = - (BasicPluralJavaType) - typeConfiguration.getJavaTypeRegistry() - .getDescriptor( arrayClass ); + (BasicPluralJavaType) typeConfiguration.getJavaTypeRegistry().getDescriptor( arrayClass ); final JdbcTypeIndicators currentBaseSqlTypeIndicators = typeConfiguration.getCurrentBaseSqlTypeIndicators(); return arrayJavaType.resolveType( typeConfiguration, @@ -96,7 +94,12 @@ public static BasicType resolveJsonArrayType(DomainType elementType, T new DelegatingJdbcTypeIndicators( currentBaseSqlTypeIndicators ) { @Override public Integer getExplicitJdbcTypeCode() { - return SqlTypes.JSON; + return SqlTypes.JSON_ARRAY; + } + + @Override + public int getPreferredSqlTypeCodeForArray(int elementSqlTypeCode) { + return SqlTypes.JSON_ARRAY; } } ); diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/function/array/OracleArrayAggEmulation.java b/hibernate-core/src/main/java/org/hibernate/dialect/function/array/OracleArrayAggEmulation.java index 194624b5882d..d0999e7a3892 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/function/array/OracleArrayAggEmulation.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/function/array/OracleArrayAggEmulation.java @@ -73,7 +73,7 @@ public void render( "Oracle array_agg emulation requires a basic plural return type, but resolved return type was: " + returnType ); } - final boolean returnJson = pluralType.getJdbcType().getDefaultSqlTypeCode() == SqlTypes.JSON; + final boolean returnJson = pluralType.getJdbcType().getDefaultSqlTypeCode() == SqlTypes.JSON_ARRAY; if ( returnJson ) { sqlAppender.append( "json_arrayagg(" ); } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/function/array/OracleArrayToStringFunction.java b/hibernate-core/src/main/java/org/hibernate/dialect/function/array/OracleArrayToStringFunction.java index 60dca992af13..426ede516f4b 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/function/array/OracleArrayToStringFunction.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/function/array/OracleArrayToStringFunction.java @@ -6,13 +6,22 @@ import java.util.List; +import org.hibernate.metamodel.mapping.JdbcMappingContainer; import org.hibernate.metamodel.model.domain.ReturnableType; +import org.hibernate.query.sqm.function.SelfRenderingOrderedSetAggregateFunctionSqlAstExpression; +import org.hibernate.sql.ast.Clause; import org.hibernate.sql.ast.SqlAstTranslator; import org.hibernate.sql.ast.spi.SqlAppender; import org.hibernate.sql.ast.tree.SqlAstNode; import org.hibernate.sql.ast.tree.expression.Expression; +import org.hibernate.sql.ast.tree.expression.FunctionExpression; +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; +import org.checkerframework.checker.nullness.qual.Nullable; + /** * Oracle array_to_string function. */ @@ -28,22 +37,89 @@ public void render( List sqlAstArguments, ReturnableType returnType, SqlAstTranslator walker) { - final String arrayTypeName = DdlTypeHelper.getTypeName( - ( (Expression) sqlAstArguments.get( 0 ) ).getExpressionType(), - walker.getSessionFactory().getTypeConfiguration() - ); - sqlAppender.append( arrayTypeName ); - sqlAppender.append( "_to_string(" ); - sqlAstArguments.get( 0 ).accept( walker ); - sqlAppender.append( ',' ); - sqlAstArguments.get( 1 ).accept( walker ); - if ( sqlAstArguments.size() > 2 ) { + final Expression arrayExpression = (Expression) sqlAstArguments.get( 0 ); + final JdbcMappingContainer expressionType = (arrayExpression).getExpressionType(); + if ( arrayExpression instanceof SelfRenderingOrderedSetAggregateFunctionSqlAstExpression + && ArrayAggFunction.FUNCTION_NAME.equals( ( (FunctionExpression) arrayExpression ).getFunctionName() ) ) { + final SelfRenderingOrderedSetAggregateFunctionSqlAstExpression functionExpression + = (SelfRenderingOrderedSetAggregateFunctionSqlAstExpression) arrayExpression; + // When the array argument is an aggregate expression, we access its contents directly + final Expression arrayElementExpression = (Expression) functionExpression.getArguments().get( 0 ); + final @Nullable Expression defaultExpression = + sqlAstArguments.size() > 2 ? (Expression) sqlAstArguments.get( 2 ) : null; + final List withinGroup = functionExpression.getWithinGroup(); + final Predicate filter = functionExpression.getFilter(); + + sqlAppender.append( "listagg(" ); + if ( filter != null ) { + sqlAppender.appendSql( "case when " ); + walker.getCurrentClauseStack().push( Clause.WHERE ); + filter.accept( walker ); + walker.getCurrentClauseStack().pop(); + sqlAppender.appendSql( " then " ); + } + if ( defaultExpression != null ) { + sqlAppender.append( "coalesce(" ); + } + arrayElementExpression.accept( walker ); + if ( defaultExpression != null ) { + sqlAppender.append( ',' ); + defaultExpression.accept( walker ); + sqlAppender.append( ')' ); + } + if ( filter != null ) { + sqlAppender.appendSql( " else null end" ); + } sqlAppender.append( ',' ); - sqlAstArguments.get( 2 ).accept( walker ); + sqlAstArguments.get( 1 ).accept( walker ); + sqlAppender.appendSql( ')' ); + + if ( withinGroup != null && !withinGroup.isEmpty() ) { + walker.getCurrentClauseStack().push( Clause.WITHIN_GROUP ); + sqlAppender.appendSql( " within group (order by " ); + withinGroup.get( 0 ).accept( walker ); + for ( int i = 1; i < withinGroup.size(); i++ ) { + sqlAppender.appendSql( ',' ); + withinGroup.get( i ).accept( walker ); + } + sqlAppender.appendSql( ')' ); + walker.getCurrentClauseStack().pop(); + } + } + else if ( expressionType.getSingleJdbcMapping().getJdbcType().getDefaultSqlTypeCode() == SqlTypes.JSON_ARRAY ) { + sqlAppender.append( "(select listagg(" ); + if ( sqlAstArguments.size() > 2 ) { + sqlAppender.append( "coalesce(t.v," ); + sqlAstArguments.get( 2 ).accept( walker ); + sqlAppender.append( ")," ); + } + else { + sqlAppender.append( "t.v," ); + } + + sqlAstArguments.get( 1 ).accept( walker ); + sqlAppender.append( ") from json_table(" ); + sqlAstArguments.get( 0 ).accept( walker ); + sqlAppender.append( ",'$[*]' columns (v path '$')) t)" ); } else { - sqlAppender.append( ",null" ); + final String arrayTypeName = DdlTypeHelper.getTypeName( + expressionType, + walker.getSessionFactory().getTypeConfiguration() + ); + sqlAppender.append( arrayTypeName ); + sqlAppender.append( "_to_string(" ); + sqlAstArguments.get( 0 ).accept( walker ); + sqlAppender.append( ',' ); + sqlAstArguments.get( 1 ).accept( walker ); + if ( sqlAstArguments.size() > 2 ) { + sqlAppender.append( ',' ); + sqlAstArguments.get( 2 ).accept( walker ); + } + else { + sqlAppender.append( ",null" ); + } + sqlAppender.append( ')' ); } - sqlAppender.append( ')' ); } } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/function/array/ArrayAggregateTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/function/array/ArrayAggregateTest.java index b82f31039630..e255416ba04f 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/function/array/ArrayAggregateTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/function/array/ArrayAggregateTest.java @@ -30,6 +30,7 @@ import org.hibernate.testing.orm.junit.BootstrapServiceRegistry; import org.hibernate.testing.orm.junit.DialectFeatureChecks; import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.Jira; import org.hibernate.testing.orm.junit.RequiresDialectFeature; import org.hibernate.testing.orm.junit.SessionFactory; import org.hibernate.testing.orm.junit.SessionFactoryScope; @@ -158,4 +159,15 @@ public void testNodeBuilder(SessionFactoryScope scope) { } ); } + @Test + @Jira("https://hibernate.atlassian.net/browse/HHH-19666") + public void testNonExistingArrayType(SessionFactoryScope scope) { + scope.inSession( em -> { + List results = em.createQuery( "select array_agg(e.id) within group (order by e.id) from EntityOfBasics e", Integer[].class ) + .getResultList(); + assertEquals( 1, results.size() ); + assertArrayEquals( new Integer[]{ 1, 2, 3 }, results.get( 0 ) ); + } ); + } + } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/function/array/ArrayToStringWithArrayAggregateTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/function/array/ArrayToStringWithArrayAggregateTest.java index e314615efc51..d1f42210c1e9 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/function/array/ArrayToStringWithArrayAggregateTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/function/array/ArrayToStringWithArrayAggregateTest.java @@ -7,6 +7,7 @@ import jakarta.persistence.Entity; import jakarta.persistence.Id; import jakarta.persistence.Tuple; +import org.hibernate.dialect.OracleDialect; import org.hibernate.query.criteria.JpaCriteriaQuery; import org.hibernate.query.criteria.JpaCteCriteria; import org.hibernate.query.criteria.JpaRoot; @@ -18,6 +19,7 @@ import org.hibernate.testing.orm.junit.RequiresDialectFeature; import org.hibernate.testing.orm.junit.SessionFactory; import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.hibernate.testing.orm.junit.SkipForDialect; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -72,6 +74,7 @@ public void test(SessionFactoryScope scope) { } @Test + @SkipForDialect(dialectClass = OracleDialect.class, majorVersion = 21, reason = "Oracle bug in version 21") public void testWithCte(SessionFactoryScope scope) { scope.inSession( em -> { final NodeBuilder cb = (NodeBuilder) em.getCriteriaBuilder();