Skip to content

HHH-19666 Oracle error for array_agg with element type not appearing as array in domain model #10665

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jul 30, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -84,9 +84,7 @@ public static <T> BasicType<?> resolveJsonArrayType(DomainType<T> elementType, T
final Class<?> arrayClass = Array.newInstance( elementType.getJavaType(), 0 ).getClass();
@SuppressWarnings("unchecked")
final BasicPluralJavaType<T> arrayJavaType =
(BasicPluralJavaType<T>)
typeConfiguration.getJavaTypeRegistry()
.getDescriptor( arrayClass );
(BasicPluralJavaType<T>) typeConfiguration.getJavaTypeRegistry().getDescriptor( arrayClass );
final JdbcTypeIndicators currentBaseSqlTypeIndicators = typeConfiguration.getCurrentBaseSqlTypeIndicators();
return arrayJavaType.resolveType(
typeConfiguration,
Expand All @@ -96,7 +94,12 @@ public static <T> BasicType<?> resolveJsonArrayType(DomainType<T> 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;
}
}
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(" );
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand All @@ -28,22 +37,89 @@ public void render(
List<? extends SqlAstNode> 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<SortSpecification> 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( ')' );
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<Integer[]> 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 ) );
} );
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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();
Expand Down