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 9d31dff67533c48c1b77f39f01726226f01a88eb Author: Walter Duque de Estrada <[email protected]> AuthorDate: Thu Mar 19 22:44:14 2026 -0500 hibernate 7: JpaFromProvider fixed --- .../orm/hibernate/query/JpaFromProvider.java | 38 ++++++++++++---------- .../hibernatequery/JpaFromProviderSpec.groovy | 18 +++++----- 2 files changed, 30 insertions(+), 26 deletions(-) diff --git a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/query/JpaFromProvider.java b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/query/JpaFromProvider.java index 8c1f9d65fe..7cd7b15858 100644 --- a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/query/JpaFromProvider.java +++ b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/query/JpaFromProvider.java @@ -18,16 +18,12 @@ */ package org.grails.orm.hibernate.query; -import java.util.AbstractMap; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; -import java.util.Optional; -import java.util.Set; import java.util.function.Function; import java.util.stream.Collectors; -import java.util.stream.Stream; import jakarta.persistence.FetchType; import jakarta.persistence.criteria.AbstractQuery; @@ -55,32 +51,29 @@ public class JpaFromProvider implements Cloneable { this.fromMap = new HashMap<>(fromMap); } - public JpaFromProvider(DetachedCriteria<?> detachedCriteria, AbstractQuery<?> cq, From<?, ?> root) { - this(detachedCriteria, List.of(), cq, root); - } - public JpaFromProvider( DetachedCriteria<?> detachedCriteria, List<Query.Projection> projections, - AbstractQuery<?> cq, From<?, ?> root) { - fromMap = getFromsByName(detachedCriteria, projections, cq, root); + fromMap = getFromsByName(detachedCriteria, projections, root); } public JpaFromProvider( JpaFromProvider parent, DetachedCriteria<?> detachedCriteria, List<Query.Projection> projections, - AbstractQuery<?> cq, From<?, ?> root) { fromMap = new HashMap<>(parent.fromMap); - fromMap.putAll(getFromsByName(detachedCriteria, projections, cq, root)); + fromMap.putAll(getFromsByName(detachedCriteria, projections, root)); + } + + public Map<String, From<?, ?>> getFromsByName() { + return fromMap; } - private Map<String, From<?, ?>> getFromsByName( + protected Map<String, From<?, ?>> getFromsByName( DetachedCriteria<?> detachedCriteria, List<Query.Projection> projections, - AbstractQuery<?> cq, From<?, ?> root) { var detachedAssociationCriteriaList = detachedCriteria.getCriteria().stream() .map(new DetachedAssociationFunction()) @@ -93,7 +86,7 @@ public class JpaFromProvider implements Cloneable { .filter(Objects::nonNull) .collect(Collectors.toSet()); - var projectedPaths = projections.stream() + var directProjectedPaths = projections.stream() .filter(Query.PropertyProjection.class::isInstance) .map(p -> ((Query.PropertyProjection) p).getPropertyName()) .filter(name -> name.contains(".")) @@ -107,7 +100,7 @@ public class JpaFromProvider implements Cloneable { java.util.Set<String> allPaths = new java.util.HashSet<>(); allPaths.addAll(aliasMap.keySet()); - allPaths.addAll(projectedPaths.stream() + allPaths.addAll(directProjectedPaths.stream() .filter(p -> !definedAliases.contains(p)) .toList()); allPaths.addAll(eagerPaths); @@ -126,6 +119,11 @@ public class JpaFromProvider implements Cloneable { } } + // Re-calculate projected paths to include expanded segments for LEFT join logic + var finalProjectedPaths = expandedPaths.stream() + .filter(p -> directProjectedPaths.stream().anyMatch(dp -> dp.equals(p) || dp.startsWith(p + "."))) + .collect(Collectors.toSet()); + Map<String, From<?, ?>> fromsByPath = new HashMap<>(); fromsByPath.put("root", root); @@ -145,7 +143,7 @@ public class JpaFromProvider implements Cloneable { JoinType joinType = JoinType.INNER; if (detachedCriteria.getJoinTypes().containsKey(path)) { joinType = detachedCriteria.getJoinTypes().get(path); - } else if (projectedPaths.contains(path) || eagerPaths.contains(path)) { + } else if (finalProjectedPaths.contains(path) || eagerPaths.contains(path)) { joinType = JoinType.LEFT; } @@ -208,7 +206,11 @@ public class JpaFromProvider implements Cloneable { String[] parsed = propertyName.split("\\."); if (parsed.length == SINGLE_PROPERTY) { - return fromMap.get("root").get(propertyName); + From<?, ?> root = fromMap.get("root"); + if (propertyName.equals(root.getJavaType().getSimpleName()) || propertyName.equals(root.getJavaType().getName())) { + return root; + } + return root.get(propertyName); } // Try to find the longest matching prefix in fromMap diff --git a/grails-data-hibernate7/core/src/test/groovy/grails/gorm/specs/hibernatequery/JpaFromProviderSpec.groovy b/grails-data-hibernate7/core/src/test/groovy/grails/gorm/specs/hibernatequery/JpaFromProviderSpec.groovy index 63e454b1b5..ce424454af 100644 --- a/grails-data-hibernate7/core/src/test/groovy/grails/gorm/specs/hibernatequery/JpaFromProviderSpec.groovy +++ b/grails-data-hibernate7/core/src/test/groovy/grails/gorm/specs/hibernatequery/JpaFromProviderSpec.groovy @@ -1,11 +1,13 @@ package grails.gorm.specs.hibernatequery +import org.hibernate.query.criteria.JpaCriteriaQuery + import grails.gorm.DetachedCriteria import grails.gorm.specs.HibernateGormDatastoreSpec import jakarta.persistence.criteria.From +import jakarta.persistence.criteria.Join import jakarta.persistence.criteria.Path import org.grails.orm.hibernate.query.JpaFromProvider -import grails.orm.HibernateCriteriaBuilder import grails.gorm.annotation.Entity import org.grails.datastore.gorm.GormEntity @@ -17,10 +19,10 @@ class JpaFromProviderSpec extends HibernateGormDatastoreSpec { private JpaFromProvider bare(Class clazz, From root) { def dc = new DetachedCriteria(clazz) - def cq = Mock(org.hibernate.query.criteria.JpaCriteriaQuery) { + def cq = Mock(JpaCriteriaQuery) { from(clazz) >> root } - return new JpaFromProvider(dc, cq, root) + return new JpaFromProvider(dc, [], root) } def "getFromsByName returns root for 'root' key"() { @@ -114,7 +116,7 @@ class JpaFromProviderSpec extends HibernateGormDatastoreSpec { def cq = Mock(org.hibernate.query.criteria.JpaCriteriaQuery) { from(_) >> root } - JpaFromProvider provider = new JpaFromProvider(dc, cq, root) + JpaFromProvider provider = new JpaFromProvider(dc, [], root) when: Path result = provider.getFullyQualifiedPath("myAlias.id") @@ -130,11 +132,11 @@ class JpaFromProviderSpec extends HibernateGormDatastoreSpec { From root = Mock(From) { getJavaType() >> String } - From teamJoin = Mock(From) { + Join teamJoin = Mock(Join) { getJavaType() >> String alias(_) >> it } - From clubJoin = Mock(From) { + Join clubJoin = Mock(Join) { getJavaType() >> String alias(_) >> it } @@ -145,7 +147,7 @@ class JpaFromProviderSpec extends HibernateGormDatastoreSpec { ] when: - JpaFromProvider provider = new JpaFromProvider(dc, projections, cq, root) + JpaFromProvider provider = new JpaFromProvider(dc, projections, root) then: "joins are created hierarchically" 1 * root.join("team", jakarta.persistence.criteria.JoinType.LEFT) >> teamJoin @@ -168,7 +170,7 @@ class JpaFromProviderSpec extends HibernateGormDatastoreSpec { From subRoot = Mock(From) { getJavaType() >> Integer } when: - JpaFromProvider subProvider = new JpaFromProvider(parent, subDc, [], subCq, subRoot) + JpaFromProvider subProvider = new JpaFromProvider(parent, subDc, [], subRoot) then: "subquery provider has its own root" subProvider.getFullyQualifiedPath("root") == subRoot
