From b7ea6ba6bf33aa904890f255a02428923799f358 Mon Sep 17 00:00:00 2001 From: stringintech Date: Sat, 28 Dec 2024 00:21:56 +0330 Subject: [PATCH 1/2] HHH-18981 test reproducing the issue --- .../ArrayToStringWithArrayAggregateTest.java | 143 ++++++++++++++++++ 1 file changed, 143 insertions(+) create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/function/array/ArrayToStringWithArrayAggregateTest.java 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 new file mode 100644 index 000000000000..d24268b15d9d --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/function/array/ArrayToStringWithArrayAggregateTest.java @@ -0,0 +1,143 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.function.array; + +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.Tuple; +import org.hibernate.dialect.H2Dialect; +import org.hibernate.dialect.HSQLDialect; +import org.hibernate.query.criteria.JpaCriteriaQuery; +import org.hibernate.query.criteria.JpaCteCriteria; +import org.hibernate.query.criteria.JpaRoot; +import org.hibernate.query.sqm.NodeBuilder; +import org.hibernate.testing.orm.junit.DialectFeatureChecks.SupportsArrayToString; +import org.hibernate.testing.orm.junit.DialectFeatureChecks.SupportsJsonArrayAgg; +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.JiraKey; +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; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Kowsar Atazadeh + */ +@DomainModel( + annotatedClasses = {ArrayToStringWithArrayAggregateTest.Book.class, ArrayToStringWithArrayAggregateTest.Dummy.class}) +@SessionFactory +@RequiresDialectFeature(feature = SupportsArrayToString.class) +@RequiresDialectFeature( feature = SupportsJsonArrayAgg.class) +@SkipForDialect(dialectClass = H2Dialect.class, reason = "Generated SQL query is not correct") +@SkipForDialect(dialectClass = HSQLDialect.class, reason = "Generated SQL query is not correct") +@JiraKey("HHH-18981") +public class ArrayToStringWithArrayAggregateTest { + + @BeforeEach + public void prepareData(SessionFactoryScope scope) { + scope.inTransaction( em -> { + em.persist( new Book( 1, "title1" ) ); + em.persist( new Book( 2, "title2" ) ); + } ); + } + + @AfterEach + public void cleanup(SessionFactoryScope scope) { + scope.inTransaction( em -> { + em.createMutationQuery( "delete from Book" ).executeUpdate(); + } ); + } + + @Test + public void test(SessionFactoryScope scope) { + scope.inSession( em -> { + final NodeBuilder cb = (NodeBuilder) em.getCriteriaBuilder(); + final JpaCriteriaQuery q = cb.createTupleQuery(); + JpaRoot root = q.from( Book.class ); + + q.multiselect( + cb.arrayToString( + cb.arrayAgg( cb.asc( root.get( "title" ) ), root.get( "title" ) ), + "," + ).alias( "titles" ) + ); + List list = em.createQuery( q ).getResultList(); + String titles = list.get( 0 ).get( "titles", String.class ); + assertThat( titles ).isEqualTo( "title1,title2" ); + } ); + } + + @Test + public void testWithCte(SessionFactoryScope scope) { + scope.inSession( em -> { + final NodeBuilder cb = (NodeBuilder) em.getCriteriaBuilder(); + + JpaCriteriaQuery cteQuery = cb.createTupleQuery(); + JpaRoot cteRoot = cteQuery.from( Book.class ); + cteQuery.multiselect( + cb.arrayAgg( cb.asc( cteRoot.get( "title" ) ), cteRoot.get( "title" ) ) + .alias( "titles_array" ) + ); + + JpaCriteriaQuery query = cb.createTupleQuery(); + JpaCteCriteria titlesCte = query.with( cteQuery ); + JpaRoot root = query.from( titlesCte ); + query.multiselect( + cb.arrayToString( root.get( "titles_array" ), cb.literal( "," ) ) + .alias( "titles" ) + ); + + List list = em.createQuery( query ).getResultList(); + String titles = list.get( 0 ).get( "titles", String.class ); + assertThat( titles ).isEqualTo( "title1,title2" ); + } ); + } + + @Entity(name = "Book") + public static class Book { + @Id + private Integer id; + private String title; + + public Book() { + } + + public Book(Integer id, String title) { + this.id = id; + this.title = title; + } + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + } + + // Needed for Oracle + @Entity + static class Dummy { + @Id + Long id; + String[] theArray; + } +} From 11b0bc20e47b7206fa45f37bf3ef004c3d2e725e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C4=8Cedomir=20Igaly?= Date: Fri, 18 Jul 2025 18:02:43 +0200 Subject: [PATCH 2/2] HHH-18981 Ignore optional parameter(s) in org.hibernate.dialect.function.array.ArrayAndElementArgumentTypeResolver#resolveFunctionArgumentType --- .../function/array/ArrayAndElementArgumentTypeResolver.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/function/array/ArrayAndElementArgumentTypeResolver.java b/hibernate-core/src/main/java/org/hibernate/dialect/function/array/ArrayAndElementArgumentTypeResolver.java index aed50d7d715d..548ddaf6affd 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/function/array/ArrayAndElementArgumentTypeResolver.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/function/array/ArrayAndElementArgumentTypeResolver.java @@ -37,6 +37,9 @@ public ArrayAndElementArgumentTypeResolver(int arrayIndex, int... elementIndexes public @Nullable MappingModelExpressible resolveFunctionArgumentType(List> arguments, int argumentIndex, SqmToSqlAstConverter converter) { if ( argumentIndex == arrayIndex ) { for ( int elementIndex : elementIndexes ) { + if ( elementIndex >= arguments.size() ) { + continue; + } final SqmTypedNode node = arguments.get( elementIndex ); if ( node instanceof SqmExpression sqmExpression ) { final MappingModelExpressible expressible = converter.determineValueMapping( sqmExpression );