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

davydotcom pushed a commit to branch fix-detachedcriteria-join-get-hibernate7
in repository https://gitbox.apache.org/repos/asf/grails-core.git

commit b1a61ee9e913d7143db10e23dd149cc0dd01b83e
Author: David Estes <[email protected]>
AuthorDate: Wed Feb 25 17:57:27 2026 -0500

    Auto-create aliases for joined association projections
    
    DetachedCriteria join('club') with property('club.name') failed unless an 
explicit createAlias('club','club') was provided. The query path for detached 
projections adapted projections directly and did not normalize nested 
association property paths into Hibernate aliases.\n\nAdd projection-property 
normalization in AbstractHibernateQuery to auto-create aliases for association 
paths and map property-based projections to aliased paths before adapting to 
Hibernate projections.\n\nAdd reg [...]
---
 .../hibernate/query/AbstractHibernateQuery.java    | 88 ++++++++++++++++++++--
 .../gorm/specs/DetachedCriteriaJoinSpec.groovy     | 24 ++++++
 2 files changed, 104 insertions(+), 8 deletions(-)

diff --git 
a/grails-data-hibernate5/core/src/main/groovy/org/grails/orm/hibernate/query/AbstractHibernateQuery.java
 
b/grails-data-hibernate5/core/src/main/groovy/org/grails/orm/hibernate/query/AbstractHibernateQuery.java
index ef96b55d66..8bb56963c5 100644
--- 
a/grails-data-hibernate5/core/src/main/groovy/org/grails/orm/hibernate/query/AbstractHibernateQuery.java
+++ 
b/grails-data-hibernate5/core/src/main/groovy/org/grails/orm/hibernate/query/AbstractHibernateQuery.java
@@ -685,6 +685,78 @@ public abstract class AbstractHibernateQuery extends Query 
{
         c.addOrder(order.isIgnoreCase() ? hibernateOrder.ignoreCase() : 
hibernateOrder);
     }
 
+    private String calculateProjectionPropertyName(String propertyName) {
+        int firstDot = propertyName.indexOf('.');
+        if (firstDot < 0) {
+            return calculatePropertyName(propertyName);
+        }
+
+        PersistentEntity currentEntity = getEntity();
+        String currentAlias = null;
+        StringBuilder associationPath = new StringBuilder();
+        String[] tokens = propertyName.split("\\.");
+
+        for (int i = 0; i < tokens.length - 1; i++) {
+            String token = tokens[i];
+            PersistentProperty persistentProperty = currentEntity != null ? 
currentEntity.getPropertyByName(token) : null;
+            if (!(persistentProperty instanceof Association) || 
persistentProperty instanceof Embedded) {
+                return calculatePropertyName(propertyName);
+            }
+
+            if (associationPath.length() > 0) {
+                associationPath.append('.');
+            }
+            associationPath.append(token);
+
+            CriteriaAndAlias criteriaAndAlias = 
getOrCreateAlias(associationPath.toString(), generateAlias(token));
+            if (criteriaAndAlias == null) {
+                return calculatePropertyName(propertyName);
+            }
+            currentAlias = criteriaAndAlias.alias;
+            currentEntity = ((Association) 
persistentProperty).getAssociatedEntity();
+        }
+
+        if (currentAlias == null) {
+            return calculatePropertyName(propertyName);
+        }
+        return currentAlias + '.' + tokens[tokens.length - 1];
+    }
+
+    private Query.Projection normalizeProjectionPropertyPath(Query.Projection 
projection) {
+        if (!(projection instanceof Query.PropertyProjection)) {
+            return projection;
+        }
+
+        String propertyName = ((Query.PropertyProjection) 
projection).getPropertyName();
+        String normalizedPropertyName = 
calculateProjectionPropertyName(propertyName);
+        if (propertyName.equals(normalizedPropertyName)) {
+            return projection;
+        }
+
+        if (projection instanceof Query.DistinctPropertyProjection) {
+            return 
org.grails.datastore.mapping.query.Projections.distinct(normalizedPropertyName);
+        }
+        if (projection instanceof Query.CountDistinctProjection) {
+            return 
org.grails.datastore.mapping.query.Projections.countDistinct(normalizedPropertyName);
+        }
+        if (projection instanceof Query.GroupPropertyProjection) {
+            return 
org.grails.datastore.mapping.query.Projections.groupProperty(normalizedPropertyName);
+        }
+        if (projection instanceof Query.SumProjection) {
+            return 
org.grails.datastore.mapping.query.Projections.sum(normalizedPropertyName);
+        }
+        if (projection instanceof Query.MinProjection) {
+            return 
org.grails.datastore.mapping.query.Projections.min(normalizedPropertyName);
+        }
+        if (projection instanceof Query.MaxProjection) {
+            return 
org.grails.datastore.mapping.query.Projections.max(normalizedPropertyName);
+        }
+        if (projection instanceof Query.AvgProjection) {
+            return 
org.grails.datastore.mapping.query.Projections.avg(normalizedPropertyName);
+        }
+        return 
org.grails.datastore.mapping.query.Projections.property(normalizedPropertyName);
+    }
+
     @Override
     public Query join(String property) {
         this.hasJoins = true;
@@ -957,19 +1029,19 @@ public abstract class AbstractHibernateQuery extends 
Query {
 
         @Override
         public ProjectionList add(Projection p) {
-            projectionList.add(new 
HibernateProjectionAdapter(p).toHibernateProjection());
+            projectionList.add(new 
HibernateProjectionAdapter(normalizeProjectionPropertyPath(p)).toHibernateProjection());
             return this;
         }
 
         @Override
         public org.grails.datastore.mapping.query.api.ProjectionList 
countDistinct(String property) {
-            
projectionList.add(Projections.countDistinct(calculatePropertyName(property)));
+            
projectionList.add(Projections.countDistinct(calculateProjectionPropertyName(property)));
             return this;
         }
 
         @Override
         public org.grails.datastore.mapping.query.api.ProjectionList 
distinct(String property) {
-            
projectionList.add(Projections.distinct(Projections.property(calculatePropertyName(property))));
+            
projectionList.add(Projections.distinct(Projections.property(calculateProjectionPropertyName(property))));
             return this;
         }
 
@@ -995,31 +1067,31 @@ public abstract class AbstractHibernateQuery extends 
Query {
 
         @Override
         public ProjectionList property(String name) {
-            
projectionList.add(Projections.property(calculatePropertyName(name)));
+            
projectionList.add(Projections.property(calculateProjectionPropertyName(name)));
             return this;
         }
 
         @Override
         public ProjectionList sum(String name) {
-            projectionList.add(Projections.sum(calculatePropertyName(name)));
+            
projectionList.add(Projections.sum(calculateProjectionPropertyName(name)));
             return this;
         }
 
         @Override
         public ProjectionList min(String name) {
-            projectionList.add(Projections.min(calculatePropertyName(name)));
+            
projectionList.add(Projections.min(calculateProjectionPropertyName(name)));
             return this;
         }
 
         @Override
         public ProjectionList max(String name) {
-            projectionList.add(Projections.max(calculatePropertyName(name)));
+            
projectionList.add(Projections.max(calculateProjectionPropertyName(name)));
             return this;
         }
 
         @Override
         public ProjectionList avg(String name) {
-            projectionList.add(Projections.avg(calculatePropertyName(name)));
+            
projectionList.add(Projections.avg(calculateProjectionPropertyName(name)));
             return this;
         }
 
diff --git 
a/grails-data-hibernate5/core/src/test/groovy/grails/gorm/specs/DetachedCriteriaJoinSpec.groovy
 
b/grails-data-hibernate5/core/src/test/groovy/grails/gorm/specs/DetachedCriteriaJoinSpec.groovy
index 1193e3eeee..1beabf3a83 100644
--- 
a/grails-data-hibernate5/core/src/test/groovy/grails/gorm/specs/DetachedCriteriaJoinSpec.groovy
+++ 
b/grails-data-hibernate5/core/src/test/groovy/grails/gorm/specs/DetachedCriteriaJoinSpec.groovy
@@ -104,4 +104,28 @@ class DetachedCriteriaJoinSpec extends 
GrailsDataTckSpec<GrailsDataHibernate5Tck
         team != null
         Hibernate.isInitialized(team.club)
     }
+
+    def 'check list with join and projected association property works without 
explicit alias'() {
+        given:
+        def club = new Club(name: 'Milan').save(flush: true)
+        new Team(name: 'Rossoneri', club: club).save(flush: true)
+
+        when:
+        def result = Team.where { name == 'Rossoneri' 
}.join('club').property('club.name').list()
+
+        then:
+        result == ['Milan']
+    }
+
+    def 'check get with join and projected association property works without 
explicit alias'() {
+        given:
+        def club = new Club(name: 'Inter').save(flush: true)
+        new Team(name: 'Nerazzurri', club: club).save(flush: true)
+
+        when:
+        def result = Team.where { name == 'Nerazzurri' 
}.join('club').property('club.name').get()
+
+        then:
+        result == 'Inter'
+    }
 }

Reply via email to