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 <[email protected]> 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) {
