Skip to content

Commit 656eb4d

Browse files
committed
HHH-19666 Oracle error for array_agg with element type not appearing as array in domain model
1 parent ca59579 commit 656eb4d

File tree

5 files changed

+112
-18
lines changed

5 files changed

+112
-18
lines changed

hibernate-core/src/main/java/org/hibernate/dialect/function/array/JsonArrayViaElementArgumentReturnTypeResolver.java

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -84,9 +84,7 @@ public static <T> BasicType<?> resolveJsonArrayType(DomainType<T> elementType, T
8484
final Class<?> arrayClass = Array.newInstance( elementType.getJavaType(), 0 ).getClass();
8585
@SuppressWarnings("unchecked")
8686
final BasicPluralJavaType<T> arrayJavaType =
87-
(BasicPluralJavaType<T>)
88-
typeConfiguration.getJavaTypeRegistry()
89-
.getDescriptor( arrayClass );
87+
(BasicPluralJavaType<T>) typeConfiguration.getJavaTypeRegistry().getDescriptor( arrayClass );
9088
final JdbcTypeIndicators currentBaseSqlTypeIndicators = typeConfiguration.getCurrentBaseSqlTypeIndicators();
9189
return arrayJavaType.resolveType(
9290
typeConfiguration,
@@ -96,7 +94,12 @@ public static <T> BasicType<?> resolveJsonArrayType(DomainType<T> elementType, T
9694
new DelegatingJdbcTypeIndicators( currentBaseSqlTypeIndicators ) {
9795
@Override
9896
public Integer getExplicitJdbcTypeCode() {
99-
return SqlTypes.JSON;
97+
return SqlTypes.JSON_ARRAY;
98+
}
99+
100+
@Override
101+
public int getPreferredSqlTypeCodeForArray(int elementSqlTypeCode) {
102+
return SqlTypes.JSON_ARRAY;
100103
}
101104
}
102105
);

hibernate-core/src/main/java/org/hibernate/dialect/function/array/OracleArrayAggEmulation.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ public void render(
7373
"Oracle array_agg emulation requires a basic plural return type, but resolved return type was: " + returnType
7474
);
7575
}
76-
final boolean returnJson = pluralType.getJdbcType().getDefaultSqlTypeCode() == SqlTypes.JSON;
76+
final boolean returnJson = pluralType.getJdbcType().getDefaultSqlTypeCode() == SqlTypes.JSON_ARRAY;
7777
if ( returnJson ) {
7878
sqlAppender.append( "json_arrayagg(" );
7979
}

hibernate-core/src/main/java/org/hibernate/dialect/function/array/OracleArrayToStringFunction.java

Lines changed: 89 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,22 @@
66

77
import java.util.List;
88

9+
import org.hibernate.metamodel.mapping.JdbcMappingContainer;
910
import org.hibernate.metamodel.model.domain.ReturnableType;
11+
import org.hibernate.query.sqm.function.SelfRenderingOrderedSetAggregateFunctionSqlAstExpression;
12+
import org.hibernate.sql.ast.Clause;
1013
import org.hibernate.sql.ast.SqlAstTranslator;
1114
import org.hibernate.sql.ast.spi.SqlAppender;
1215
import org.hibernate.sql.ast.tree.SqlAstNode;
1316
import org.hibernate.sql.ast.tree.expression.Expression;
17+
import org.hibernate.sql.ast.tree.expression.FunctionExpression;
18+
import org.hibernate.sql.ast.tree.predicate.Predicate;
19+
import org.hibernate.sql.ast.tree.select.SortSpecification;
20+
import org.hibernate.type.SqlTypes;
1421
import org.hibernate.type.spi.TypeConfiguration;
1522

23+
import org.checkerframework.checker.nullness.qual.Nullable;
24+
1625
/**
1726
* Oracle array_to_string function.
1827
*/
@@ -28,22 +37,89 @@ public void render(
2837
List<? extends SqlAstNode> sqlAstArguments,
2938
ReturnableType<?> returnType,
3039
SqlAstTranslator<?> walker) {
31-
final String arrayTypeName = DdlTypeHelper.getTypeName(
32-
( (Expression) sqlAstArguments.get( 0 ) ).getExpressionType(),
33-
walker.getSessionFactory().getTypeConfiguration()
34-
);
35-
sqlAppender.append( arrayTypeName );
36-
sqlAppender.append( "_to_string(" );
37-
sqlAstArguments.get( 0 ).accept( walker );
38-
sqlAppender.append( ',' );
39-
sqlAstArguments.get( 1 ).accept( walker );
40-
if ( sqlAstArguments.size() > 2 ) {
40+
final Expression arrayExpression = (Expression) sqlAstArguments.get( 0 );
41+
final JdbcMappingContainer expressionType = (arrayExpression).getExpressionType();
42+
if ( arrayExpression instanceof SelfRenderingOrderedSetAggregateFunctionSqlAstExpression
43+
&& ArrayAggFunction.FUNCTION_NAME.equals( ( (FunctionExpression) arrayExpression ).getFunctionName() ) ) {
44+
final SelfRenderingOrderedSetAggregateFunctionSqlAstExpression functionExpression
45+
= (SelfRenderingOrderedSetAggregateFunctionSqlAstExpression) arrayExpression;
46+
// When the array argument is an aggregate expression, we access its contents directly
47+
final Expression arrayElementExpression = (Expression) functionExpression.getArguments().get( 0 );
48+
final @Nullable Expression defaultExpression =
49+
sqlAstArguments.size() > 2 ? (Expression) sqlAstArguments.get( 2 ) : null;
50+
final List<SortSpecification> withinGroup = functionExpression.getWithinGroup();
51+
final Predicate filter = functionExpression.getFilter();
52+
53+
sqlAppender.append( "listagg(" );
54+
if ( filter != null ) {
55+
sqlAppender.appendSql( "case when " );
56+
walker.getCurrentClauseStack().push( Clause.WHERE );
57+
filter.accept( walker );
58+
walker.getCurrentClauseStack().pop();
59+
sqlAppender.appendSql( " then " );
60+
}
61+
if ( defaultExpression != null ) {
62+
sqlAppender.append( "coalesce(" );
63+
}
64+
arrayElementExpression.accept( walker );
65+
if ( defaultExpression != null ) {
66+
sqlAppender.append( ',' );
67+
defaultExpression.accept( walker );
68+
sqlAppender.append( ')' );
69+
}
70+
if ( filter != null ) {
71+
sqlAppender.appendSql( " else null end" );
72+
}
4173
sqlAppender.append( ',' );
42-
sqlAstArguments.get( 2 ).accept( walker );
74+
sqlAstArguments.get( 1 ).accept( walker );
75+
sqlAppender.appendSql( ')' );
76+
77+
if ( withinGroup != null && !withinGroup.isEmpty() ) {
78+
walker.getCurrentClauseStack().push( Clause.WITHIN_GROUP );
79+
sqlAppender.appendSql( " within group (order by " );
80+
withinGroup.get( 0 ).accept( walker );
81+
for ( int i = 1; i < withinGroup.size(); i++ ) {
82+
sqlAppender.appendSql( ',' );
83+
withinGroup.get( i ).accept( walker );
84+
}
85+
sqlAppender.appendSql( ')' );
86+
walker.getCurrentClauseStack().pop();
87+
}
88+
}
89+
else if ( expressionType.getSingleJdbcMapping().getJdbcType().getDefaultSqlTypeCode() == SqlTypes.JSON_ARRAY ) {
90+
sqlAppender.append( "(select listagg(" );
91+
if ( sqlAstArguments.size() > 2 ) {
92+
sqlAppender.append( "coalesce(t.v," );
93+
sqlAstArguments.get( 2 ).accept( walker );
94+
sqlAppender.append( ")," );
95+
}
96+
else {
97+
sqlAppender.append( "t.v," );
98+
}
99+
100+
sqlAstArguments.get( 1 ).accept( walker );
101+
sqlAppender.append( ") from json_table(" );
102+
sqlAstArguments.get( 0 ).accept( walker );
103+
sqlAppender.append( ",'$[*]' columns (v path '$')) t)" );
43104
}
44105
else {
45-
sqlAppender.append( ",null" );
106+
final String arrayTypeName = DdlTypeHelper.getTypeName(
107+
expressionType,
108+
walker.getSessionFactory().getTypeConfiguration()
109+
);
110+
sqlAppender.append( arrayTypeName );
111+
sqlAppender.append( "_to_string(" );
112+
sqlAstArguments.get( 0 ).accept( walker );
113+
sqlAppender.append( ',' );
114+
sqlAstArguments.get( 1 ).accept( walker );
115+
if ( sqlAstArguments.size() > 2 ) {
116+
sqlAppender.append( ',' );
117+
sqlAstArguments.get( 2 ).accept( walker );
118+
}
119+
else {
120+
sqlAppender.append( ",null" );
121+
}
122+
sqlAppender.append( ')' );
46123
}
47-
sqlAppender.append( ')' );
48124
}
49125
}

hibernate-core/src/test/java/org/hibernate/orm/test/function/array/ArrayAggregateTest.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import org.hibernate.testing.orm.junit.BootstrapServiceRegistry;
3131
import org.hibernate.testing.orm.junit.DialectFeatureChecks;
3232
import org.hibernate.testing.orm.junit.DomainModel;
33+
import org.hibernate.testing.orm.junit.Jira;
3334
import org.hibernate.testing.orm.junit.RequiresDialectFeature;
3435
import org.hibernate.testing.orm.junit.SessionFactory;
3536
import org.hibernate.testing.orm.junit.SessionFactoryScope;
@@ -160,4 +161,15 @@ public void testNodeBuilder(SessionFactoryScope scope) {
160161
} );
161162
}
162163

164+
@Test
165+
@Jira("https://hibernate.atlassian.net/browse/HHH-19666")
166+
public void testNonExistingArrayType(SessionFactoryScope scope) {
167+
scope.inSession( em -> {
168+
List<Integer[]> results = em.createQuery( "select array_agg(e.id) within group (order by e.id) from EntityOfBasics e", Integer[].class )
169+
.getResultList();
170+
assertEquals( 1, results.size() );
171+
assertArrayEquals( new Integer[]{ 1, 2, 3 }, results.get( 0 ) );
172+
} );
173+
}
174+
163175
}

hibernate-core/src/test/java/org/hibernate/orm/test/function/array/ArrayToStringWithArrayAggregateTest.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import jakarta.persistence.Entity;
88
import jakarta.persistence.Id;
99
import jakarta.persistence.Tuple;
10+
import org.hibernate.dialect.OracleDialect;
1011
import org.hibernate.query.criteria.JpaCriteriaQuery;
1112
import org.hibernate.query.criteria.JpaCteCriteria;
1213
import org.hibernate.query.criteria.JpaRoot;
@@ -18,6 +19,7 @@
1819
import org.hibernate.testing.orm.junit.RequiresDialectFeature;
1920
import org.hibernate.testing.orm.junit.SessionFactory;
2021
import org.hibernate.testing.orm.junit.SessionFactoryScope;
22+
import org.hibernate.testing.orm.junit.SkipForDialect;
2123
import org.junit.jupiter.api.AfterEach;
2224
import org.junit.jupiter.api.BeforeEach;
2325
import org.junit.jupiter.api.Test;
@@ -72,6 +74,7 @@ public void test(SessionFactoryScope scope) {
7274
}
7375

7476
@Test
77+
@SkipForDialect(dialectClass = OracleDialect.class, majorVersion = 21, reason = "Oracle bug in version 21")
7578
public void testWithCte(SessionFactoryScope scope) {
7679
scope.inSession( em -> {
7780
final NodeBuilder cb = (NodeBuilder) em.getCriteriaBuilder();

0 commit comments

Comments
 (0)