You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@grails.apache.org by bo...@apache.org on 2025/10/18 02:48:09 UTC

(grails-core) 03/04: fixed WhereQueryWithAssociationSortSpec

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

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

commit 63f2631dccaf9b9c240049539b72ae78dd414837
Author: Walter Duque de Estrada <wb...@mac.com>
AuthorDate: Fri Oct 17 19:32:35 2025 -0500

    fixed WhereQueryWithAssociationSortSpec
---
 .../orm/hibernate/query/JpaFromProvider.java       | 23 +++++++++++++++++++---
 .../specs/WhereQueryWithAssociationSortSpec.groovy |  4 +++-
 .../org/grails/datastore/mapping/query/Query.java  |  2 +-
 3 files changed, 24 insertions(+), 5 deletions(-)

diff --git a/grails-data-hibernate6/core/src/main/groovy/org/grails/orm/hibernate/query/JpaFromProvider.java b/grails-data-hibernate6/core/src/main/groovy/org/grails/orm/hibernate/query/JpaFromProvider.java
index 629ad2767e..1f1b67c96e 100644
--- a/grails-data-hibernate6/core/src/main/groovy/org/grails/orm/hibernate/query/JpaFromProvider.java
+++ b/grails-data-hibernate6/core/src/main/groovy/org/grails/orm/hibernate/query/JpaFromProvider.java
@@ -50,7 +50,16 @@ public class JpaFromProvider implements Cloneable {
                         .toList().stream())
                 .distinct()
                 .map(joinColumn -> {
-                    var table = detachedFroms.computeIfAbsent(joinColumn, s -> root).join(joinColumn, ((Map<String, JoinType>) detachedCriteria.getJoinTypes())
+                    // Determine owner class for this join path from detached criteria
+                    Class<?> ownerClass = Optional.ofNullable(aliasMap.get(joinColumn))
+                            .map(dac -> dac.getAssociation().getOwner().getJavaClass())
+                            .orElse(root.getJavaType());
+                    // Choose base From: use outer root only if join belongs to the outer root type; otherwise create a detached root for the owner
+                    From base = ownerClass.equals(root.getJavaType())
+                            ? root
+                            : detachedFroms.computeIfAbsent(joinColumn, s -> cq.from(ownerClass));
+
+                    var table = base.join(joinColumn, ((Map<String, JoinType>) detachedCriteria.getJoinTypes())
                             .entrySet()
                             .stream()
                             .filter(entry -> entry.getKey().equals(joinColumn))
@@ -63,7 +72,7 @@ public class JpaFromProvider implements Cloneable {
                             .orElse(joinColumn);
                     table.alias(column);
                     return new AbstractMap.SimpleEntry<>(column, table);
-        }).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
+        }).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (existing, replacement) -> existing, java.util.LinkedHashMap::new));
         fromsByName.put("root", root);
         return fromsByName;
     }
@@ -77,9 +86,17 @@ public class JpaFromProvider implements Cloneable {
     }
 
     private Map<String, DetachedAssociationCriteria> createAliasMap(List<DetachedAssociationCriteria> detachedAssociationCriteriaList) {
+        // Use a merge function and a stable map type to avoid DuplicateKey exceptions when the same
+        // association path/alias appears multiple times (e.g., referenced in both predicate and sort).
+        // Keep the first occurrence to preserve deterministic aliasing.
         return detachedAssociationCriteriaList.stream()
                 .map(new AliasMapEntryFunction())
-                .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
+                .collect(Collectors.toMap(
+                        Map.Entry::getKey,
+                        Map.Entry::getValue,
+                        (existing, replacement) -> existing,
+                        java.util.LinkedHashMap::new
+                ));
     }
 
     public Path getFullyQualifiedPath(String propertyName) {
diff --git a/grails-data-hibernate6/core/src/test/groovy/grails/gorm/specs/WhereQueryWithAssociationSortSpec.groovy b/grails-data-hibernate6/core/src/test/groovy/grails/gorm/specs/WhereQueryWithAssociationSortSpec.groovy
index 15c1813f4c..4d7e42da4b 100644
--- a/grails-data-hibernate6/core/src/test/groovy/grails/gorm/specs/WhereQueryWithAssociationSortSpec.groovy
+++ b/grails-data-hibernate6/core/src/test/groovy/grails/gorm/specs/WhereQueryWithAssociationSortSpec.groovy
@@ -72,7 +72,9 @@ class WhereQueryWithAssociationSortSpec extends GrailsDataTckSpec<GrailsDataHibe
 
 
         then: "an exception is thrown because no alias is specified"
-        thrown QueryException
+        results.size() == 1
+        results.first().name == "MU First Team"
+
 
 
         when: "a where query uses a sort on an association"
diff --git a/grails-datastore-core/src/main/groovy/org/grails/datastore/mapping/query/Query.java b/grails-datastore-core/src/main/groovy/org/grails/datastore/mapping/query/Query.java
index 896fe61b75..3aa7fb603e 100644
--- a/grails-datastore-core/src/main/groovy/org/grails/datastore/mapping/query/Query.java
+++ b/grails-datastore-core/src/main/groovy/org/grails/datastore/mapping/query/Query.java
@@ -68,7 +68,7 @@ public abstract class Query implements Cloneable {
     protected boolean uniqueResult;
     protected Map<String, FetchType> fetchStrategies = new HashMap<>();
     protected Map<String, JoinType> joinTypes = new HashMap<>();
-    protected boolean queryCache;
+    protected Boolean queryCache;
     protected LockModeType lockResult;
 
     protected Query(Session session, PersistentEntity entity) {