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 3d4d4be350384efdc96466e27f75cd3831d563fd
Author: Walter Duque de Estrada <[email protected]>
AuthorDate: Sat Mar 21 23:06:34 2026 -0500

    hibernate 7:
      Key Enhancements & Fixes:
    
       * Fixed Many-to-Many Persistence: Removed an erroneous setInverse call 
in ManyToManyElementBinder that was hardcoding bidirectional associations as 
inverse. This restored the correct GORM ownership
         rules and fixed issues where join table entries were not being created.
       * Resolved SemanticException in Subqueries: Refactored 
PredicateGenerator to accurately infer the Java return type for IN subqueries. 
It now dynamically detects projections—including aliased and nested
         properties (e.g., e1.id)—to ensure the subquery result matches the 
type expected by the Hibernate 7 SQM engine.
       * Fixed Subquery Alias Isolation: Improved PredicateGenerator to use a 
specialized JpaFromProvider constructor for subqueries. This correctly isolates 
and registers subquery-specific aliases, preventing
         PathElementException when using DetachedCriteria within a main query.
---
 grails-data-hibernate7/core/ISSUES.md              |  3 +-
 .../secondpass/ManyToManyElementBinder.java        |  1 -
 .../orm/hibernate/query/PredicateGenerator.java    | 38 +++++++++++++++-------
 .../WhereQueryOldIssueVerificationSpec.groovy      | 10 +++---
 .../hibernatequery/PredicateGeneratorSpec.groovy   | 16 +++++++++
 .../orm/HibernateCriteriaBuilderDirectSpec.groovy  | 37 ++++++++++++++++++++-
 .../secondpass/ManyToManyElementBinderSpec.groovy  | 17 ----------
 7 files changed, 84 insertions(+), 38 deletions(-)

diff --git a/grails-data-hibernate7/core/ISSUES.md 
b/grails-data-hibernate7/core/ISSUES.md
index 14503ccf09..524d83cb6d 100644
--- a/grails-data-hibernate7/core/ISSUES.md
+++ b/grails-data-hibernate7/core/ISSUES.md
@@ -1,6 +1,5 @@
 # Known Issues in Hibernate 7 Migration
-DetachedCriteriaProjectionAliasSpec
-WhereQueryOldIssueVerificationSpec
+
 
 
 
diff --git 
a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/ManyToManyElementBinder.java
 
b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/ManyToManyElementBinder.java
index f44024af30..43408eedd1 100644
--- 
a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/ManyToManyElementBinder.java
+++ 
b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/ManyToManyElementBinder.java
@@ -46,6 +46,5 @@ public class ManyToManyElementBinder {
         Collection collection = property.getCollection();
         collection.setElement(element);
         
collectionForPropertyConfigBinder.bindCollectionForPropertyConfig(property);
-        collection.setInverse(!property.isCircular());
     }
 }
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 2a4ba1f7d2..4b141302b0 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
@@ -23,12 +23,12 @@ import jakarta.persistence.criteria.Subquery;
 import org.hibernate.query.criteria.HibernateCriteriaBuilder;
 import org.hibernate.query.criteria.JpaInPredicate;
 import org.hibernate.query.sqm.tree.domain.SqmPath;
-import org.hibernate.query.sqm.tree.predicate.SqmInListPredicate;
 import org.slf4j.Logger;
 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;
@@ -404,13 +404,35 @@ public class PredicateGenerator {
         var path = 
fromsByProvider.getFullyQualifiedPath(criterion.getProperty());
         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();
+        Class<?> subqueryType = subEntity.getJavaClass();
+        if (projection instanceof Query.PropertyProjection propertyProjection) 
{
+            PersistentProperty prop = 
subEntity.getPropertyByName(propertyProjection.getPropertyName());
+            if (prop != null) {
+                subqueryType = prop.getType();
+            } else if (propertyProjection.getPropertyName().contains(".")) {
+                // Handle aliased or nested properties in projections (e.g., 
"e1.id")
+                String propName = propertyProjection.getPropertyName();
+                String simplePropName = 
propName.substring(propName.lastIndexOf('.') + 1);
+                PersistentProperty simpleProp = 
subEntity.getPropertyByName(simplePropName);
+                if (simpleProp != null) {
+                    subqueryType = simpleProp.getType();
+                } else if (simplePropName.equals("id")) {
+                    subqueryType = subEntity.getIdentity() != null ? 
subEntity.getIdentity().getType() : Long.class;
+                }
+            }
+        } else if (projection instanceof Query.IdProjection) {
+            subqueryType = subEntity.getIdentity() != null ? 
subEntity.getIdentity().getType() : Long.class;
+        } else if (isAssociation) {
+            subqueryType = subEntity.getIdentity() != null ? 
subEntity.getIdentity().getType() : Long.class;
+        }
+
+        var subquery = criteriaQuery.subquery(subqueryType);
         var from = subquery.from(subEntity.getJavaClass());
-        var clonedProviderByName = (JpaFromProvider) fromsByProvider.clone();
-        clonedProviderByName.put("root", from);
+        var clonedProviderByName = new JpaFromProvider(fromsByProvider, 
(DetachedCriteria<?>) queryableCriteria, java.util.Collections.emptyList(), 
from);
         var predicates = getPredicates(cb, criteriaQuery, from, 
queryableCriteria.getCriteria(), clonedProviderByName, subEntity);
-        
subquery.select(clonedProviderByName.getFullyQualifiedPath(subProperty)).distinct(true).where(cb.and(predicates));
+        subquery.select((Expression) 
clonedProviderByName.getFullyQualifiedPath(subProperty)).distinct(true).where(cb.and(predicates));
         return in.value(subquery);
     }
 
@@ -459,12 +481,6 @@ public class PredicateGenerator {
                 : ((Query.NotIn) criterion).getSubquery();
     }
 
-    private Class getJavaTypeOfInClause(SqmInListPredicate predicate) {
-        return 
Optional.ofNullable(predicate.getTestExpression().getExpressible())
-                .map(expressible -> 
expressible.getExpressibleJavaType().getJavaTypeClass())
-                .orElse(null);
-    }
-
     private Number getNumericValue(Query.PropertyCriterion criterion) {
         Object value = criterion.getValue();
         if (value != null) {
diff --git 
a/grails-data-hibernate7/core/src/test/groovy/grails/gorm/specs/WhereQueryOldIssueVerificationSpec.groovy
 
b/grails-data-hibernate7/core/src/test/groovy/grails/gorm/specs/WhereQueryOldIssueVerificationSpec.groovy
index b0d844cf8f..d2664b220a 100644
--- 
a/grails-data-hibernate7/core/src/test/groovy/grails/gorm/specs/WhereQueryOldIssueVerificationSpec.groovy
+++ 
b/grails-data-hibernate7/core/src/test/groovy/grails/gorm/specs/WhereQueryOldIssueVerificationSpec.groovy
@@ -237,13 +237,11 @@ class WhereQueryOldIssueVerificationSpec extends 
Specification {
     @Issue('https://github.com/apache/grails-core/issues/14600')
     def "findAllBy works with bidirectional hasMany relation"() {
         given: "authors with books in a bidirectional hasMany"
-        def author1 = new WqBiAuthor(name: "Stephen King").save(flush: true)
-        def book1 = new WqBiBook(title: "IT").save(flush: true)
-        def book2 = new WqBiBook(title: "The Shining").save(flush: true)
+        def author1 = new WqBiAuthor(name: "Stephen King")
+        def book1 = new WqBiBook(title: "IT")
+        def book2 = new WqBiBook(title: "The Shining")
         author1.addToBooks(book1)
         author1.addToBooks(book2)
-        book1.addToAuthors(author1)
-        book2.addToAuthors(author1)
         author1.save(flush: true)
 
         when: "using withCriteria to find books by author"
@@ -362,7 +360,7 @@ class WqBiBook implements HibernateEntity<WqBiBook> {
     String title
 
     static hasMany = [authors: WqBiAuthor]
-    static belongsTo = WqBiAuthor
+    static belongsTo = [WqBiAuthor]
 }
 
 @Entity
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 ac0f2b5da9..30e7fdac86 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
@@ -105,6 +105,22 @@ class PredicateGeneratorSpec extends 
HibernateGormDatastoreSpec {
         predicates.length == 1
     }
 
+    def "test getPredicates with subquery aliases"() {
+        given: "a subquery with an alias"
+        def subCriteria = new 
DetachedCriteria(PredicateGeneratorSpecPet).build {
+            createAlias('face', 'f')
+            eq('f.name', 'Funny')
+        }
+        List criteria = [new Query.In("id", subCriteria)]
+
+        when:
+        def predicates = predicateGenerator.getPredicates(cb, query, root, 
criteria, fromProvider, personEntity)
+
+        then: "the alias 'f' is correctly resolved"
+        noExceptionThrown()
+        predicates.length == 1
+    }
+
     def "test getPredicates with Disjunction"() {
         given:
         List criteria = [new Query.Disjunction()
diff --git 
a/grails-data-hibernate7/core/src/test/groovy/grails/orm/HibernateCriteriaBuilderDirectSpec.groovy
 
b/grails-data-hibernate7/core/src/test/groovy/grails/orm/HibernateCriteriaBuilderDirectSpec.groovy
index 07d8f0ba55..177334cbfa 100644
--- 
a/grails-data-hibernate7/core/src/test/groovy/grails/orm/HibernateCriteriaBuilderDirectSpec.groovy
+++ 
b/grails-data-hibernate7/core/src/test/groovy/grails/orm/HibernateCriteriaBuilderDirectSpec.groovy
@@ -42,7 +42,7 @@ class HibernateCriteriaBuilderDirectSpec extends 
HibernateGormDatastoreSpec {
     @Shared HibernateCriteriaBuilder builder
 
     def setupSpec() {
-        manager.addAllDomainClasses([DirectAccount, DirectTransaction])
+        manager.addAllDomainClasses([DirectAccount, DirectTransaction, 
DirectBiBook, DirectBiAuthor])
     }
 
     def setup() {
@@ -52,6 +52,28 @@ class HibernateCriteriaBuilderDirectSpec extends 
HibernateGormDatastoreSpec {
                 manager.hibernateDatastore)
     }
 
+    void "test bidirectional many-to-many with subquery alias resolution"() {
+        given: "authors with books in a bidirectional hasMany"
+        def author1 = new DirectBiAuthor(name: "Stephen King")
+        def book1 = new DirectBiBook(title: "IT")
+        def book2 = new DirectBiBook(title: "The Shining")
+        author1.addToBooks(book1)
+        author1.addToBooks(book2)
+        author1.save(flush: true)
+
+        def b = new HibernateCriteriaBuilder(DirectBiBook, 
manager.hibernateDatastore.sessionFactory, manager.hibernateDatastore)
+
+        when: "using withCriteria to find books by author"
+        def books = b.list {
+            authors {
+                'in'('id', [author1.id])
+            }
+        }
+
+        then: "books are found without error"
+        books.size() == 2
+    }
+
     // ─── DSL integration: data-driven scenarios ────────────────────────────
 
     def setupData() {
@@ -669,3 +691,16 @@ class DirectTransaction {
     BigDecimal amount
     static belongsTo = [account: DirectAccount]
 }
+
+@Entity
+class DirectBiBook {
+    String title
+    static hasMany = [authors: DirectBiAuthor]
+    static belongsTo = [DirectBiAuthor]
+}
+
+@Entity
+class DirectBiAuthor {
+    String name
+    static hasMany = [books: DirectBiBook]
+}
diff --git 
a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/ManyToManyElementBinderSpec.groovy
 
b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/ManyToManyElementBinderSpec.groovy
index 52a1dae3be..5993fe60e3 100644
--- 
a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/ManyToManyElementBinderSpec.groovy
+++ 
b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/ManyToManyElementBinderSpec.groovy
@@ -59,23 +59,6 @@ class ManyToManyElementBinderSpec extends 
HibernateGormDatastoreSpec {
         collection.getElement() instanceof ManyToOne
         (collection.getElement() as ManyToOne).getReferencedEntityName() == 
MTMEItem.name
     }
-
-    def "bind sets collection inverse false for a circular many-to-many"() {
-        given:
-        def property = propertyFor(MTMESubtype, "related")
-        def mbc = getGrailsDomainBinder().getMetadataBuildingContext()
-        def collection = new Bag(mbc, null)
-        collection.setCollectionTable(new Table("test", 
"mtme_subtype_mtme_base"))
-
-        property.setCollection(collection)
-
-        when:
-        binder.bind(property)
-
-        then:
-        property.isCircular()
-        !collection.isInverse()
-    }
 }
 
 @Entity

Reply via email to