You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@grails.apache.org by da...@apache.org on 2026/02/26 00:26:22 UTC
(grails-core) branch fix-detachedcriteria-join-get-hibernate7 created (now 72cbb2f598)
This is an automated email from the ASF dual-hosted git repository.
davydotcom pushed a change to branch fix-detachedcriteria-join-get-hibernate7
in repository https://gitbox.apache.org/repos/asf/grails-core.git
at 72cbb2f598 adding one more test spec
This branch includes the following new commits:
new 6e2fb58aef Fix DetachedCriteria join handling for get() in Hibernate
new b1a61ee9e9 Auto-create aliases for joined association projections
new 72cbb2f598 adding one more test spec
The 3 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
(grails-core) 02/03: Auto-create aliases for joined association projections
Posted by da...@apache.org.
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 <da...@gmail.com>
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'
+ }
}
(grails-core) 03/03: adding one more test spec
Posted by da...@apache.org.
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 72cbb2f598a4df569a0bd26e148396693458c4f4
Author: David Estes <da...@gmail.com>
AuthorDate: Wed Feb 25 18:11:58 2026 -0500
adding one more test spec
---
.../gorm/specs/DetachedCriteriaJoinSpec.groovy | 30 ++++++++++++++++++++++
1 file changed, 30 insertions(+)
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 1beabf3a83..ad028f391f 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
@@ -128,4 +128,34 @@ class DetachedCriteriaJoinSpec extends GrailsDataTckSpec<GrailsDataHibernate5Tck
then:
result == 'Inter'
}
+
+ def 'check list with association subquery plus join and projection works'() {
+ given:
+ def club = new Club(name: 'Ajax').save(flush: true)
+ new Team(name: 'Amsterdammers', club: club).save(flush: true)
+
+ when:
+ def result = Team.where {
+ club {
+ name == 'Ajax'
+ }
+ }.join('club').property('club.name').list()
+
+ then:
+ result == ['Ajax']
+ }
+
+ def 'check list can sort by joined association property'() {
+ given:
+ def clubA = new Club(name: 'A Club').save(flush: true)
+ def clubB = new Club(name: 'B Club').save(flush: true)
+ new Team(name: 'Team B', club: clubB).save(flush: true)
+ new Team(name: 'Team A', club: clubA).save(flush: true)
+
+ when:
+ def result = Team.where {}.join('club').sort('club.name', 'asc').property('name').list()
+
+ then:
+ result == ['Team A', 'Team B']
+ }
}
(grails-core) 01/03: Fix DetachedCriteria join handling for get() in Hibernate
Posted by da...@apache.org.
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 6e2fb58aef3f2ecb136a6793cecc7457cf6bbc68
Author: David Estes <da...@gmail.com>
AuthorDate: Wed Feb 25 17:49:57 2026 -0500
Fix DetachedCriteria join handling for get() in Hibernate
DetachedCriteria joins with explicit join types were not updating HibernateQuery join state used by single-result execution. This caused get()/singleResult() paths to diverge from list() behavior for join fetch application.\n\nAdd an override of join(String, JoinType) in AbstractHibernateQuery that sets hasJoins, records joinTypes, and applies FetchMode.JOIN to criteria/detachedCriteria.\n\nAlso add a regression test in DetachedCriteriaJoinSpec proving Team.where { ... }.join('club'). [...]
---
.../grails/orm/hibernate/query/AbstractHibernateQuery.java | 11 +++++++++++
.../grails/gorm/specs/DetachedCriteriaJoinSpec.groovy | 14 ++++++++++++++
2 files changed, 25 insertions(+)
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 eed1e54bea..ef96b55d66 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
@@ -695,6 +695,17 @@ public abstract class AbstractHibernateQuery extends Query {
return this;
}
+ @Override
+ public Query join(String property, JoinType joinType) {
+ this.hasJoins = true;
+ this.joinTypes.put(property, joinType);
+ if (criteria != null)
+ criteria.setFetchMode(property, FetchMode.JOIN);
+ else if (detachedCriteria != null)
+ detachedCriteria.setFetchMode(property, FetchMode.JOIN);
+ return this;
+ }
+
@Override
public Query select(String property) {
this.hasJoins = true;
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 4cf03df933..1193e3eeee 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
@@ -25,6 +25,7 @@ import org.apache.grails.data.hibernate5.core.GrailsDataHibernate5TckManager
import org.apache.grails.data.testing.tck.base.GrailsDataTckSpec
import org.grails.datastore.gorm.finders.DynamicFinder
import org.grails.orm.hibernate.query.HibernateQuery
+import org.hibernate.Hibernate
import jakarta.persistence.criteria.JoinType
@@ -90,4 +91,17 @@ class DetachedCriteriaJoinSpec extends GrailsDataTckSpec<GrailsDataHibernate5Tck
expect:
joinType == org.hibernate.sql.JoinType.RIGHT_OUTER_JOIN
}
+
+ def 'check get honours join and eagerly loads association'() {
+ given:
+ def club = new Club(name: 'Juventus').save(flush: true)
+ new Team(name: 'Torino', club: club).save(flush: true)
+
+ when:
+ Team team = Team.where { name == 'Torino' }.join('club').get()
+
+ then:
+ team != null
+ Hibernate.isInitialized(team.club)
+ }
}