This is an automated email from the ASF dual-hosted git repository.

borinquenkid pushed a commit to branch 8.0.x-hibernate7-dev
in repository https://gitbox.apache.org/repos/asf/grails-core.git

commit 182b06144b19c452085a3574526175debefe75bb
Author: Walter Duque de Estrada <[email protected]>
AuthorDate: Fri Mar 20 08:15:59 2026 -0500

    hibernate 7:
    
     PredicateGenerator cleaned up and spec fixed
---
 grails-data-hibernate7/core/ISSUES.md              | 33 ++---------
 .../orm/hibernate/query/PredicateGenerator.java    | 50 ++++++++--------
 .../hibernatequery/PredicateGeneratorSpec.groovy   | 68 +++++++++++++++++++++-
 3 files changed, 95 insertions(+), 56 deletions(-)

diff --git a/grails-data-hibernate7/core/ISSUES.md 
b/grails-data-hibernate7/core/ISSUES.md
index d27598ec91..b7e1d73a9a 100644
--- a/grails-data-hibernate7/core/ISSUES.md
+++ b/grails-data-hibernate7/core/ISSUES.md
@@ -5,15 +5,14 @@
 BasicCollectionInQuerySpec
 ByteBuddyGroovyInterceptorSpec
 DetachedAssociationFunctionSpec
+DetachedCriteriaProjectionAliasSpec
 HibernateMappingFactorySpec
 HibernateProxyHandler7Spec
-JpaCriteriaQueryCreatorSpec
-JpaFromProviderSpec
-PredicateGeneratorSpec
-WhereQueryBugFixSpec
 WhereQueryOldIssueVerificationSpec
 
 
+---
+
 ### 3. ByteBuddy Proxy Initialization & Interception
 **Symptoms:**
 - `ByteBuddyGroovyInterceptorSpec` and `HibernateProxyHandler7Spec` failures.
@@ -24,17 +23,8 @@ Hibernate 7's `ByteBuddyInterceptor.intercept()` does not 
distinguish between ac
 
 ---
 
-### 4. JpaFromProvider & JpaCriteriaQueryCreator (Joins and Aliases)
-**Symptoms:**
-- `NullPointerException: Cannot invoke 
"jakarta.persistence.criteria.Join.alias(String)" because "table" is null`
-- Association projection paths fail to resolve correctly in complex queries.
-
-**Description:**
-Referencing an association in a projection (e.g., `projections { 
property('owner.name') }`) requires an automatic join that wasn't previously 
necessary or was handled differently. The fix in `JpaFromProvider` requires 
robust mock handling in tests to avoid NPEs during alias assignment.
-
 ---
 
-
 ---
 
 ### 6. MappingException: Class 'java.util.Set' does not implement 
'UserCollectionType'
@@ -43,18 +33,9 @@ Referencing an association in a projection (e.g., 
`projections { property('owner
 - Affects `BasicCollectionInQuerySpec`.
 
 **Description:**
-Hibernate 7 changed how collection types are resolved. Some tests using 
`hasMany` with default collection types are failing because Hibernate 7 expects 
a specific `UserCollectionType` implementation when a custom type is inferred 
or explicitly mapped.
+Hibernate 7 changed how collection types are resolved. Some tests using 
`hasMany` with default collection types are failing during 
`buildSessionFactory`.
 
 ---
-
-### 7. TerminalPathException in SQM Paths
-**Symptoms:**
-- `org.hibernate.query.sqm.TerminalPathException: Terminal path 'id' has no 
attribute 'id'`
-- Affects `PredicateGeneratorSpec` and `WhereQueryBugFixSpec`.
-
-**Description:**
-In Hibernate 7, once a path is resolved to a terminal attribute (like `id`), 
further navigation on that path (e.g., trying to access a property on the ID) 
triggers this exception. This affects how GORM constructs subqueries and 
criteria filters.
-
 ---
 
 ### 8. IDENTITY Generator Default in TCK
@@ -66,9 +47,3 @@ The TCK Manager now globally sets `id generator: 'identity'` 
to avoid `SequenceS
 
 ---
 
-### 9. HibernateGormStaticApi HQL Overloads
-**Symptoms:**
-- `HibernateGormStaticApiSpec` failures related to `executeQuery` and 
`executeUpdate`.
-
-**Description:**
-Hibernate 7's stricter query parameter rules and the removal of certain 
`Query` overloads require that HQL strings be handled carefully, especially 
when mixing positional and named parameters or passing GORM-specific options 
(like `flushMode`).
diff --git 
a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/query/PredicateGenerator.java
 
b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/query/PredicateGenerator.java
index ac635ab9b8..5d2f946ae0 100644
--- 
a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/query/PredicateGenerator.java
+++ 
b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/query/PredicateGenerator.java
@@ -29,11 +29,12 @@ import org.slf4j.LoggerFactory;
 
 import org.springframework.core.convert.ConversionService;
 
-import grails.gorm.DetachedCriteria;
 import org.grails.datastore.gorm.GormEntity;
 import org.grails.datastore.gorm.query.criteria.DetachedAssociationCriteria;
 import org.grails.datastore.mapping.core.exceptions.ConfigurationException;
 import org.grails.datastore.mapping.model.PersistentEntity;
+import org.grails.datastore.mapping.model.PersistentProperty;
+import org.grails.datastore.mapping.model.types.Association;
 import org.grails.datastore.mapping.query.Query;
 import org.grails.datastore.mapping.query.api.QueryableCriteria;
 
@@ -85,7 +86,7 @@ public class PredicateGenerator {
         } else if (criterion instanceof Query.DistinctProjection) {
             return cb.conjunction();
         } else if (criterion instanceof DetachedAssociationCriteria<?> c) {
-            return handleAssociationCriteria(cb, criteriaQuery, root, 
fromsByProvider, entity, c);
+            return handleAssociationCriteria(cb, criteriaQuery, root, 
fromsByProvider, c);
         } else if (criterion instanceof HibernateAssociationQuery haq) {
             return handleHibernateAssociationQuery(cb, criteriaQuery, root, 
fromsByProvider, haq);
         } else if (criterion instanceof Query.PropertyCriterion pc) {
@@ -95,10 +96,8 @@ public class PredicateGenerator {
         } else if (criterion instanceof Query.PropertyNameCriterion c) {
             return handlePropertyNameCriterion(cb, fromsByProvider, c);
         } else if (criterion instanceof Query.Exists c) {
-            // FIX: Pass the child entity from the subquery context
             return handleExists(cb, criteriaQuery, root, fromsByProvider, 
c.getSubquery().getPersistentEntity(), c);
         } else if (criterion instanceof Query.NotExists c) {
-            // FIX: Pass the child entity from the subquery context
             PersistentEntity childEntity = 
c.getSubquery().getPersistentEntity();
             return cb.not(handleExists(cb, criteriaQuery, root, 
fromsByProvider, childEntity, new Query.Exists(c.getSubquery())));
         }
@@ -132,14 +131,12 @@ public class PredicateGenerator {
             CriteriaQuery<?> criteriaQuery,
             From<?, ?> root,
             JpaFromProvider fromsByProvider,
-            PersistentEntity entity,
             DetachedAssociationCriteria<?> c) {
 
         var child = root.join(c.getAssociationPath(), JoinType.LEFT);
         JpaFromProvider childTablesByName = (JpaFromProvider) 
fromsByProvider.clone();
         childTablesByName.put("root", child);
 
-        // FIX: Get the target PersistentEntity from the association
         PersistentEntity associatedEntity = 
c.getAssociation().getAssociatedEntity();
 
         return cb.and(getPredicates(
@@ -148,7 +145,7 @@ public class PredicateGenerator {
                 child,
                 c.getCriteria(),
                 childTablesByName,
-                associatedEntity // Pass the entity, not the association
+                associatedEntity
         ));
     }
 
@@ -161,7 +158,6 @@ public class PredicateGenerator {
         var child = root.join(haq.associationPath, JoinType.LEFT);
         JpaFromProvider childFroms = (JpaFromProvider) fromsByProvider.clone();
         childFroms.put("root", child);
-        // haq.getEntity() is already the correct child entity
         return cb.and(getPredicates(cb, criteriaQuery, child, 
haq.getAssociationCriteria(), childFroms, haq.getEntity()));
     }
 
@@ -173,7 +169,6 @@ public class PredicateGenerator {
             PersistentEntity entity,
             Query.PropertyCriterion pc) {
 
-        // Firewall: validate property against the current context's entity
         String propertyName = pc.getProperty();
         if (!"id".equals(propertyName) && !propertyName.contains(".") && 
entity.getPropertyByName(propertyName) == null) {
             throw new ConfigurationException("Property [" + propertyName +
@@ -185,7 +180,7 @@ public class PredicateGenerator {
         if (pc instanceof Query.NotIn c) {
             return handleNotIn(cb, criteriaQuery, fromsByProvider, entity, c, 
fullyQualifiedPath);
         } else if (pc instanceof Query.SubqueryCriterion c) {
-            return handleSubqueryCriterion(cb, criteriaQuery, fromsByProvider, 
entity, c);
+            return handleSubqueryCriterion(cb, criteriaQuery, fromsByProvider, 
c);
         } else if (pc instanceof Query.In c) {
             return handleIn(cb, criteriaQuery, fromsByProvider, entity, c, 
fullyQualifiedPath);
         } else if (pc instanceof Query.ILike c) {
@@ -244,12 +239,10 @@ public class PredicateGenerator {
             var projection = c.getSubquery().getProjections().get(0);
             if (projection instanceof Query.PropertyProjection pp) {
                 boolean distinct = projection instanceof 
Query.DistinctPropertyProjection;
-                // FIX: Pass subEntity
                 Predicate[] predicates2 = getPredicates(cb, criteriaQuery, 
from2, c.getValue().getCriteria(), newMap2, subEntity);
                 
subquery2.select(from2.get(pp.getPropertyName())).distinct(distinct).where(cb.and(predicates2));
                 return cb.not(cb.in(fullyQualifiedPath).value(subquery2));
             } else if (projection instanceof Query.IdProjection) {
-                // FIX: Pass subEntity
                 Predicate[] predicates2 = getPredicates(cb, criteriaQuery, 
from2, c.getValue().getCriteria(), newMap2, subEntity);
                 subquery2.select(from2).where(cb.and(predicates2));
                 return cb.not(cb.in(fullyQualifiedPath).value(subquery2));
@@ -289,14 +282,12 @@ public class PredicateGenerator {
             HibernateCriteriaBuilder cb,
             CriteriaQuery<?> criteriaQuery,
             JpaFromProvider fromsByProvider,
-            PersistentEntity entity,
             Query.SubqueryCriterion c) {
         Subquery subquery = criteriaQuery.subquery(Number.class);
         PersistentEntity subEntity = c.getValue().getPersistentEntity();
         Root from = subquery.from(subEntity.getJavaClass());
-        JpaFromProvider newMap = new JpaFromProvider(fromsByProvider, 
(DetachedCriteria) c.getValue(), List.of(), criteriaQuery, from);
+        JpaFromProvider newMap = (JpaFromProvider) fromsByProvider.clone();
         newMap.put("root", from);
-        // FIX: Pass subEntity to subquery recursion
         Predicate[] predicates = getPredicates(cb, criteriaQuery, from, 
c.getValue().getCriteria(), newMap, subEntity);
         Path path = fromsByProvider.getFullyQualifiedPath(c.getProperty());
 
@@ -370,13 +361,12 @@ public class PredicateGenerator {
             CriteriaQuery<?> criteriaQuery,
             From<?, ?> root_,
             JpaFromProvider fromsByProvider,
-            PersistentEntity entity, // This is now correctly the child entity
+            PersistentEntity entity,
             Query.Exists c) {
         Subquery subquery = criteriaQuery.subquery(Integer.class);
         Root subRoot = subquery.from(entity.getJavaClass());
-        JpaFromProvider newMap = new JpaFromProvider(fromsByProvider, 
(DetachedCriteria) c.getSubquery(), List.of(), criteriaQuery, subRoot);
+        JpaFromProvider newMap = (JpaFromProvider) fromsByProvider.clone();
         newMap.put("root", subRoot);
-        // Pass 'entity' (which is child) to recursion
         var predicates = getPredicates(cb, criteriaQuery, subRoot, 
c.getSubquery().getCriteria(), newMap, entity);
         var existsPredicate = getExistsPredicate(cb, root_, entity, subRoot);
         Predicate[] allPredicates = existsPredicate != null
@@ -396,18 +386,26 @@ public class PredicateGenerator {
         var projection = findPropertyOrIdProjection(queryableCriteria);
         var subProperty = findSubproperty(projection);
         var path = 
fromsByProvider.getFullyQualifiedPath(criterion.getProperty());
-        var in = findInPredicate(cb, projection, path, subProperty);
+        boolean isAssociation = isAssociation(entity, criterion.getProperty());
+        var in = findInPredicate(cb, projection, path, subProperty, 
isAssociation);
         var subquery = 
criteriaQuery.subquery(getJavaTypeOfInClause((SqmInListPredicate) in));
         PersistentEntity subEntity = queryableCriteria.getPersistentEntity();
         var from = subquery.from(subEntity.getJavaClass());
-        var clonedProviderByName = new JpaFromProvider(fromsByProvider, 
(DetachedCriteria) queryableCriteria, List.of(), criteriaQuery, from);
+        var clonedProviderByName = (JpaFromProvider) fromsByProvider.clone();
         clonedProviderByName.put("root", from);
-        // FIX: Pass subEntity
         var predicates = getPredicates(cb, criteriaQuery, from, 
queryableCriteria.getCriteria(), clonedProviderByName, subEntity);
         
subquery.select(clonedProviderByName.getFullyQualifiedPath(subProperty)).distinct(true).where(cb.and(predicates));
         return in.value(subquery);
     }
 
+    private boolean isAssociation(PersistentEntity entity, String 
propertyName) {
+        if (propertyName.equals("id") || (entity.getIdentity() != null && 
propertyName.equals(entity.getIdentity().getName()))) {
+            return false;
+        }
+        PersistentProperty prop = entity.getPropertyByName(propertyName);
+        return prop instanceof Association;
+    }
+
     private Predicate getExistsPredicate(
             HibernateCriteriaBuilder cb, From<?, ?> root_, PersistentEntity 
childPersistentEntity, Root subRoot) {
         return childPersistentEntity.getAssociations().stream()
@@ -418,8 +416,12 @@ public class PredicateGenerator {
     }
 
     private JpaInPredicate findInPredicate(
-            HibernateCriteriaBuilder cb, Object projection, Path path, String 
subProperty) {
-        return projection instanceof Query.PropertyProjection ? cb.in(path) : 
cb.in(((SqmPath) path).get(subProperty));
+            HibernateCriteriaBuilder cb, Object projection, Path path, String 
subProperty, boolean isAssociation) {
+        if (projection instanceof Query.PropertyProjection || !isAssociation) {
+            return cb.in(path);
+        } else {
+            return cb.in(((SqmPath) path).get(subProperty));
+        }
     }
 
     private String findSubproperty(Object projection) {
@@ -466,4 +468,4 @@ public class PredicateGenerator {
                 criterion.getProperty(),
                 "null"));
     }
-}
\ No newline at end of file
+}
diff --git 
a/grails-data-hibernate7/core/src/test/groovy/grails/gorm/specs/hibernatequery/PredicateGeneratorSpec.groovy
 
b/grails-data-hibernate7/core/src/test/groovy/grails/gorm/specs/hibernatequery/PredicateGeneratorSpec.groovy
index 4a813a1373..5fbd821263 100644
--- 
a/grails-data-hibernate7/core/src/test/groovy/grails/gorm/specs/hibernatequery/PredicateGeneratorSpec.groovy
+++ 
b/grails-data-hibernate7/core/src/test/groovy/grails/gorm/specs/hibernatequery/PredicateGeneratorSpec.groovy
@@ -1,15 +1,16 @@
 package grails.gorm.specs.hibernatequery
 
+import org.hibernate.query.criteria.HibernateCriteriaBuilder
+
 import grails.gorm.DetachedCriteria
 import grails.gorm.specs.HibernateGormDatastoreSpec
 import jakarta.persistence.criteria.CriteriaQuery
 import jakarta.persistence.criteria.Root
 import org.grails.datastore.mapping.model.PersistentEntity
 import org.grails.datastore.mapping.query.Query
-import grails.orm.HibernateCriteriaBuilder
+
 import org.grails.orm.hibernate.query.JpaFromProvider
 import org.grails.orm.hibernate.query.PredicateGenerator
-import spock.lang.Shared
 import grails.gorm.annotation.Entity
 import org.grails.datastore.gorm.GormEntity
 
@@ -31,7 +32,7 @@ class PredicateGeneratorSpec extends 
HibernateGormDatastoreSpec {
         query = cb.createQuery(PredicateGeneratorSpecPerson)
         root = query.from(PredicateGeneratorSpecPerson)
         personEntity = 
session.datastore.mappingContext.getPersistentEntity(PredicateGeneratorSpecPerson.name)
-        fromProvider = new JpaFromProvider(new 
DetachedCriteria(PredicateGeneratorSpecPerson), query, root)
+        fromProvider = new JpaFromProvider(new 
DetachedCriteria(PredicateGeneratorSpecPerson),[], root)
     }
 
     def "test getPredicates with Equals criterion"() {
@@ -103,6 +104,66 @@ class PredicateGeneratorSpec extends 
HibernateGormDatastoreSpec {
         noExceptionThrown()
         predicates.length == 1
     }
+
+    def "test getPredicates with Disjunction"() {
+        given:
+        List criteria = [new Query.Disjunction()
+                                 .add(new Query.Equals("firstName", "Bob"))
+                                 .add(new Query.Equals("firstName", "Alice"))]
+
+        when:
+        def predicates = predicateGenerator.getPredicates(cb, query, root, 
criteria, fromProvider, personEntity)
+
+        then:
+        predicates.length == 1
+    }
+
+    def "test getPredicates with Negation"() {
+        given:
+        List criteria = [new Query.Negation().add(new 
Query.Equals("firstName", "Bob"))]
+
+        when:
+        def predicates = predicateGenerator.getPredicates(cb, query, root, 
criteria, fromProvider, personEntity)
+
+        then:
+        predicates.length == 1
+    }
+
+    def "test getPredicates with Property Comparison"() {
+        given:
+        List criteria = [new Query.EqualsProperty("firstName", "lastName")]
+
+        when:
+        def predicates = predicateGenerator.getPredicates(cb, query, root, 
criteria, fromProvider, personEntity)
+
+        then:
+        predicates.length == 1
+    }
+
+    def "test getPredicates with Like and ILike"() {
+        given:
+        List criteria = [
+            new Query.Like("firstName", "B%"),
+            new Query.ILike("firstName", "b%")
+        ]
+
+        when:
+        def predicates = predicateGenerator.getPredicates(cb, query, root, 
criteria, fromProvider, personEntity)
+
+        then:
+        predicates.length == 2
+    }
+
+    def "test getPredicates with Size Comparison"() {
+        given:
+        List criteria = [new Query.SizeEquals("pets", 2)]
+
+        when:
+        def predicates = predicateGenerator.getPredicates(cb, query, root, 
criteria, fromProvider, personEntity)
+
+        then:
+        predicates.length == 1
+    }
 }
 
 @Entity
@@ -112,6 +173,7 @@ class PredicateGeneratorSpecPerson implements 
GormEntity<PredicateGeneratorSpecP
     String lastName
     Integer age
     PredicateGeneratorSpecFace face
+    static hasMany = [pets: PredicateGeneratorSpecPet]
 }
 
 @Entity

Reply via email to