This is an automated email from the ASF dual-hosted git repository. borinquenkid pushed a commit to branch merge-hibernate6 in repository https://gitbox.apache.org/repos/asf/grails-core.git
commit b033331bdf7a66e9fd42b9293d7a4e8febddec13 Author: Walter Duque de Estrada <[email protected]> AuthorDate: Sat Jun 28 01:44:32 2025 -0400 Exists and Not Exists Predicates --- .../orm/hibernate/query/PredicateGenerator.java | 57 ++++++++++++++---- .../specs/hibernatequery/HibernateQuerySpec.groovy | 68 +++++----------------- 2 files changed, 60 insertions(+), 65 deletions(-) diff --git a/grails-data-hibernate6/core/src/main/groovy/org/grails/orm/hibernate/query/PredicateGenerator.java b/grails-data-hibernate6/core/src/main/groovy/org/grails/orm/hibernate/query/PredicateGenerator.java index e8aadd5628..f1c09f5272 100644 --- a/grails-data-hibernate6/core/src/main/groovy/org/grails/orm/hibernate/query/PredicateGenerator.java +++ b/grails-data-hibernate6/core/src/main/groovy/org/grails/orm/hibernate/query/PredicateGenerator.java @@ -11,12 +11,15 @@ import jakarta.persistence.criteria.Predicate; import jakarta.persistence.criteria.Root; import jakarta.persistence.criteria.Subquery; import org.grails.datastore.gorm.query.criteria.DetachedAssociationCriteria; +import org.grails.datastore.mapping.model.PersistentEntity; +import org.grails.datastore.mapping.model.types.Association; import org.grails.datastore.mapping.query.Query; import org.grails.datastore.mapping.query.api.QueryableCriteria; import org.hibernate.query.criteria.HibernateCriteriaBuilder; import org.hibernate.query.criteria.JpaInPredicate; import org.hibernate.query.criteria.JpaJoin; import org.hibernate.query.criteria.JpaPath; +import org.hibernate.query.criteria.JpaPredicate; import org.hibernate.query.sqm.tree.domain.SqmPath; import org.hibernate.query.sqm.tree.domain.SqmSetJoin; import org.hibernate.query.sqm.tree.predicate.SqmInListPredicate; @@ -28,12 +31,14 @@ import java.time.LocalDate; import java.time.LocalDateTime; import java.time.OffsetDateTime; import java.time.ZonedDateTime; +import java.util.Arrays; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; +import java.util.stream.Stream; @Slf4j @@ -182,21 +187,43 @@ public class PredicateGenerator { return cb.not(cb.in(fromsByProvider.getFullyQualifiedPath(c.getProperty()), c.getValue())); } } else if (criterion instanceof Query.Exists c) { - Subquery subquery = criteriaQuery.subquery(Object.class); - Root subRoot = subquery.from(c.getSubquery().getPersistentEntity().getJavaClass()); + Subquery subquery = criteriaQuery.subquery(Integer.class); + PersistentEntity childPersistentEntity = c.getSubquery().getPersistentEntity(); + Root subRoot = subquery.from(childPersistentEntity.getJavaClass()); + + JpaFromProvider newMap = (JpaFromProvider) fromsByProvider.clone(); newMap.put("root", subRoot); - Predicate[] predicates = getPredicates(cb, criteriaQuery, subRoot, c.getSubquery().getCriteria(), newMap); - subquery.select(cb.literal(1)).where(cb.and(predicates)); - return cb.exists(subquery); + var predicates = getPredicates(cb, criteriaQuery, subRoot, c.getSubquery().getCriteria(), newMap); + + var existsPredicate = getExistsPredicate(cb, root_, childPersistentEntity, subRoot); + Predicate[] allPredicates = Stream.concat( + Arrays.stream(predicates), + Stream.of(existsPredicate) + ).toArray(Predicate[]::new); + + subquery.select(cb.literal(1)).where(cb.and(allPredicates)); + JpaPredicate exists = cb.exists(subquery); + return exists; } else if (criterion instanceof Query.NotExists c) { - Subquery subquery = criteriaQuery.subquery(Object.class); - Root subRoot = subquery.from(c.getSubquery().getPersistentEntity().getJavaClass()); + Subquery subquery = criteriaQuery.subquery(Integer.class); + PersistentEntity childPersistentEntity = c.getSubquery().getPersistentEntity(); + Root subRoot = subquery.from(childPersistentEntity.getJavaClass()); + + JpaFromProvider newMap = (JpaFromProvider) fromsByProvider.clone(); newMap.put("root", subRoot); - Predicate[] predicates = getPredicates(cb, criteriaQuery, subRoot, c.getSubquery().getCriteria(), fromsByProvider); - subquery.select(cb.literal(1)).where(cb.and(predicates)); - return cb.not(cb.exists(subquery)); + var predicates = getPredicates(cb, criteriaQuery, subRoot, c.getSubquery().getCriteria(), newMap); + + var existsPredicate = getExistsPredicate(cb, root_, childPersistentEntity, subRoot); + Predicate[] allPredicates = Stream.concat( + Arrays.stream(predicates), + Stream.of(existsPredicate) + ).toArray(Predicate[]::new); + + subquery.select(cb.literal(1)).where(cb.and(allPredicates)); + JpaPredicate exists = cb.exists(subquery); + return cb.not(exists); } else if (criterion instanceof Query.SubqueryCriterion c) { Subquery subquery = criteriaQuery.subquery(Number.class); Root from = subquery.from(c.getValue().getPersistentEntity().getJavaClass()); @@ -257,6 +284,16 @@ public class PredicateGenerator { return list.toArray(new Predicate[0]); } + private static Predicate getExistsPredicate(HibernateCriteriaBuilder cb, From root_, PersistentEntity childPersistentEntity, Root subRoot) { + Association owner = childPersistentEntity + .getAssociations() + .stream() + .filter(assoc -> assoc.getAssociatedEntity().getJavaClass().equals(root_.getJavaType())) + .findFirst().orElseThrow(); + Predicate existsPredicate = cb.equal(subRoot.get(owner.getName()), root_); + return existsPredicate; + } + @SuppressWarnings("rawtypes") private static JpaInPredicate findInPredicate(HibernateCriteriaBuilder cb, Object projection, Path path, String subProperty) { return projection instanceof Query.PropertyProjection ? cb.in(path) : cb.in(((SqmPath) path).get(subProperty)); diff --git a/grails-data-hibernate6/core/src/test/groovy/grails/gorm/specs/hibernatequery/HibernateQuerySpec.groovy b/grails-data-hibernate6/core/src/test/groovy/grails/gorm/specs/hibernatequery/HibernateQuerySpec.groovy index b7b008e635..614daf4c0a 100644 --- a/grails-data-hibernate6/core/src/test/groovy/grails/gorm/specs/hibernatequery/HibernateQuerySpec.groovy +++ b/grails-data-hibernate6/core/src/test/groovy/grails/gorm/specs/hibernatequery/HibernateQuerySpec.groovy @@ -2,12 +2,17 @@ package grails.gorm.specs.hibernatequery import grails.gorm.DetachedCriteria import grails.gorm.specs.HibernateGormDatastoreSpec +import jakarta.persistence.criteria.CriteriaQuery import jakarta.persistence.criteria.JoinType +import jakarta.persistence.criteria.Path +import jakarta.persistence.criteria.Root +import jakarta.persistence.criteria.Subquery import org.apache.grails.data.testing.tck.domains.* import org.grails.datastore.mapping.query.Query import org.grails.orm.hibernate.AbstractHibernateSession import org.grails.orm.hibernate.HibernateDatastore import org.grails.orm.hibernate.query.HibernateQuery +import org.hibernate.query.criteria.JpaPredicate import spock.lang.Ignore @@ -327,37 +332,14 @@ class HibernateQuerySpec extends HibernateGormDatastoreSpec { oldBob == newBob } -// @Ignore("Exits subquery is broken") - /** - * org.grails.orm.hibernate.query.PredicateGenerator.getPredicates() - * else if (criterion instanceof Query.Exists c) - select - p1_0.id, - p1_0.age, - p1_0.face_id, - p1_0.first_name, - p1_0.last_name, - p1_0.my_boolean_property, - p1_0.version - from - person p1_0 - where - exists(select - 1 - from - pet p2_0 - where - p2_0.owner_id=?) - offset - ? rows - */ def exists() { given: new Person(firstName: "Fred", lastName: "Rogers", age: 52).save(flush: true) new Pet(name: "Lucky", owner: oldBob).save(flush:true) hibernateQuery.exists( - new DetachedCriteria(Pet).property("name").eq("owner", oldBob) + new DetachedCriteria(Pet) ) + when: def list = hibernateQuery.list() then: @@ -365,40 +347,16 @@ class HibernateQuerySpec extends HibernateGormDatastoreSpec { oldBob == list.get(0) } - /** - * org.grails.orm.hibernate.query.PredicateGenerator.getPredicates() - * else if (criterion instanceof Query.NotExists c) - select - p1_0.id, - p1_0.age, - p1_0.face_id, - p1_0.first_name, - p1_0.last_name, - p1_0.my_boolean_property, - p1_0.version - from - person p1_0 - where - not exists(select - 1 - from - pet p2_0 - where - p2_0.owner_id=?) - offset - ? rows - */ + def notExists() { given: - def fred = new Person(firstName: "Fred", lastName: "Rogers", age: 52).save(flush: true) - def oldPet = new Pet(name: "Lucky") - oldBob.addToPets(oldPet) - oldBob.save(flush: true) - hibernateQuery.notExits(new DetachedCriteria(Pet).eq("owner", fred)) + def newBob = new Person(firstName: "Fred", lastName: "Rogers", age: 52).save(flush: true) + new Pet(name: "Lucky", owner: newBob).save(flush:true) + hibernateQuery.notExits(new DetachedCriteria(Pet)) when: - def newBob = hibernateQuery.singleResult() + def result = hibernateQuery.singleResult() then: - oldBob == newBob + oldBob == result } def greaterThanAll() {
