Skip to content

Commit f5cf899

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

File tree

3 files changed

+104
-19
lines changed

3 files changed

+104
-19
lines changed

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

Lines changed: 88 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -8,19 +8,21 @@
88

99
import java.util.List;
1010

11+
import org.hibernate.metamodel.mapping.JdbcMappingContainer;
1112
import org.hibernate.query.ReturnableType;
12-
import org.hibernate.query.sqm.function.AbstractSqmSelfRenderingFunctionDescriptor;
13-
import org.hibernate.query.sqm.produce.function.ArgumentTypesValidator;
14-
import org.hibernate.query.sqm.produce.function.StandardArgumentsValidators;
15-
import org.hibernate.query.sqm.produce.function.StandardFunctionArgumentTypeResolvers;
13+
import org.hibernate.query.sqm.function.SelfRenderingOrderedSetAggregateFunctionSqlAstExpression;
14+
import org.hibernate.sql.ast.Clause;
1615
import org.hibernate.sql.ast.SqlAstTranslator;
1716
import org.hibernate.sql.ast.spi.SqlAppender;
1817
import org.hibernate.sql.ast.tree.SqlAstNode;
1918
import org.hibernate.sql.ast.tree.expression.Expression;
19+
import org.hibernate.sql.ast.tree.expression.FunctionExpression;
20+
import org.hibernate.sql.ast.tree.predicate.Predicate;
21+
import org.hibernate.sql.ast.tree.select.SortSpecification;
22+
import org.hibernate.type.SqlTypes;
2023
import org.hibernate.type.spi.TypeConfiguration;
2124

22-
import static org.hibernate.query.sqm.produce.function.FunctionParameterType.ANY;
23-
import static org.hibernate.query.sqm.produce.function.FunctionParameterType.INTEGER;
25+
import org.checkerframework.checker.nullness.qual.Nullable;
2426

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

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
@@ -32,6 +32,7 @@
3232
import org.hibernate.testing.orm.junit.BootstrapServiceRegistry;
3333
import org.hibernate.testing.orm.junit.DialectFeatureChecks;
3434
import org.hibernate.testing.orm.junit.DomainModel;
35+
import org.hibernate.testing.orm.junit.Jira;
3536
import org.hibernate.testing.orm.junit.RequiresDialectFeature;
3637
import org.hibernate.testing.orm.junit.SessionFactory;
3738
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: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
import jakarta.persistence.Entity;
1010
import jakarta.persistence.Id;
1111
import jakarta.persistence.Tuple;
12+
13+
import org.hibernate.dialect.OracleDialect;
1214
import org.hibernate.query.criteria.JpaCriteriaQuery;
1315
import org.hibernate.query.criteria.JpaCteCriteria;
1416
import org.hibernate.query.criteria.JpaRoot;
@@ -20,6 +22,7 @@
2022
import org.hibernate.testing.orm.junit.RequiresDialectFeature;
2123
import org.hibernate.testing.orm.junit.SessionFactory;
2224
import org.hibernate.testing.orm.junit.SessionFactoryScope;
25+
import org.hibernate.testing.orm.junit.SkipForDialect;
2326
import org.junit.jupiter.api.AfterEach;
2427
import org.junit.jupiter.api.BeforeEach;
2528
import org.junit.jupiter.api.Test;
@@ -73,6 +76,7 @@ public void test(SessionFactoryScope scope) {
7376
}
7477

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

0 commit comments

Comments
 (0)