From 4361790205a8fbce69cf85efdb4fb02552bf4af4 Mon Sep 17 00:00:00 2001 From: Andrea Boriero Date: Thu, 20 Jan 2022 13:18:18 +0100 Subject: [PATCH] Fix ArrayIndexOutOfBoundsException for CollectionJoin#on(Predicate ...) --- .../sqm/internal/SqmCriteriaNodeBuilder.java | 4 + .../jpa/compliance/CriteriaPredicateTest.java | 195 ++++++++++++++++++ 2 files changed, 199 insertions(+) create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/jpa/compliance/CriteriaPredicateTest.java diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmCriteriaNodeBuilder.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmCriteriaNodeBuilder.java index cc70ca5007..979cc3bfb3 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmCriteriaNodeBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmCriteriaNodeBuilder.java @@ -331,6 +331,10 @@ public class SqmCriteriaNodeBuilder implements NodeBuilder, SqmCreationContext, @Override @SafeVarargs public final SqmPredicate wrap(Expression... expressions) { + if ( expressions.length == 1 ) { + return wrap( expressions[0] ); + } + final SqmPredicate lhs = wrap( expressions[0] ); final SqmPredicate rhs = wrap( expressions[1] ); SqmPredicate predicate = new SqmAndPredicate( lhs, rhs, this ); diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/compliance/CriteriaPredicateTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/compliance/CriteriaPredicateTest.java new file mode 100644 index 0000000000..608b50a0d4 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/compliance/CriteriaPredicateTest.java @@ -0,0 +1,195 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.orm.test.jpa.compliance; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import org.hibernate.testing.orm.junit.EntityManagerFactoryScope; +import org.hibernate.testing.orm.junit.Jpa; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.OneToMany; +import jakarta.persistence.Table; +import jakarta.persistence.TypedQuery; +import jakarta.persistence.criteria.CollectionJoin; +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.CriteriaQuery; +import jakarta.persistence.criteria.Join; +import jakarta.persistence.criteria.JoinType; +import jakarta.persistence.criteria.Predicate; +import jakarta.persistence.criteria.Root; +import jakarta.persistence.metamodel.EntityType; + +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.hamcrest.CoreMatchers.nullValue; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; + +@Jpa( + annotatedClasses = { + CriteriaPredicateTest.Person.class, + CriteriaPredicateTest.Account.class, + CriteriaPredicateTest.Deposit.class + } +) +public class CriteriaPredicateTest { + + @BeforeEach + public void setUp(EntityManagerFactoryScope scope) { + scope.inTransaction( + entityManager -> { + final Deposit deposit = new Deposit( 1, new BigDecimal( 100 ) ); + final Deposit deposit2 = new Deposit( 2, new BigDecimal( 100 ) ); + + final Collection deposits = new ArrayList<>(); + deposits.add( deposit ); + deposits.add( deposit2 ); + + + final Account account = new Account( 1, "abc", deposits ); + final Collection accounts = new ArrayList<>(); + accounts.add( account ); + + final Person luigi = new Person( 1, "Luigi", 20, accounts ); + final Person fab = new Person( 2, "Fab", 20, null ); + entityManager.persist( deposit ); + entityManager.persist( deposit2 ); + entityManager.persist( account ); + entityManager.persist( luigi ); + entityManager.persist( fab ); + } + ); + } + + @Test + public void joinOnPredicateArrayTest(EntityManagerFactoryScope scope) { + scope.inEntityManager( + entityManager -> { + final CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder(); + + final CriteriaQuery query = criteriaBuilder.createQuery( Person.class ); + final Root person = query.from( Person.class ); + final EntityType personEntity = entityManager.getMetamodel().entity( Person.class ); + final Join accounts = person.join( personEntity.getCollection( + "accounts", + Account.class + ) ); + + final EntityType accountEntity = entityManager.getMetamodel().entity( Account.class ); + final CollectionJoin deposits = accounts + .join( accountEntity.getCollection( "deposits", Deposit.class ), JoinType.INNER ); + + Predicate pred = deposits.getOn(); + assertThat( pred, nullValue() ); + + Predicate[] predArray = { criteriaBuilder.equal( deposits.get( "id" ), "1" ) }; + + deposits.on( predArray ); + + pred = deposits.getOn(); + assertThat( pred, notNullValue() ); + + + query.select( person ); + + TypedQuery tquery = entityManager.createQuery( query ); + List persons = tquery.getResultList(); + + assertEquals( 1, persons.size() ); + assertEquals( 1, persons.get( 0 ).getId() ); + } + ); + } + + @Entity(name = "Person") + @Table(name = "PERSON_TABLE") + public static class Person { + @Id + private Integer id; + + private String name; + + private Integer age; + + @OneToMany + private Collection accounts; + + Person() { + } + + public Person(Integer id, String name, Integer age, Collection accounts) { + this.id = id; + this.name = name; + this.age = age; + this.accounts = accounts; + } + + public Integer getId() { + return id; + } + + public String getName() { + return name; + } + + public Integer getAge() { + return age; + } + + public Collection getAccounts() { + return accounts; + } + } + + @Entity(name = "Account") + @Table(name = "ACCOUNT_TABLE") + public static class Account { + @Id + private Integer id; + + private String code; + + @OneToMany + private Collection deposits; + + public Account() { + } + + public Account( + Integer id, + String code, + Collection deposits) { + this.id = id; + this.code = code; + this.deposits = deposits; + } + } + + @Entity(name = "Deposit") + @Table(name = "DEPOSIT_TABLE") + public static class Deposit { + @Id + private Integer id; + + private BigDecimal amount; + + public Deposit() { + } + + public Deposit(Integer id, BigDecimal amount) { + this.id = id; + this.amount = amount; + } + } + +}