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

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

commit 4e15fb4fd047c2a7c7cf1cb4918c0e81aa79821c
Author: Walter Duque de Estrada <[email protected]>
AuthorDate: Thu Feb 19 09:23:56 2026 -0600

    Refactor second pass binders and move bindOrderBy logic
---
 .../cfg/domainbinding/binder/CollectionBinder.java |   2 +-
 .../hibernate/HibernateToManyProperty.java         |  48 ------
 .../secondpass/CollectionSecondPassBinder.java     |  34 +++-
 .../secondpass/MapSecondPassBinder.java            |  24 ++-
 .../hibernate/HibernateToManyPropertySpec.groovy   | 157 -----------------
 .../CollectionSecondPassBinderSpec.groovy          | 186 +++++++++++++++++++++
 .../ListSecondPassBinderSpec.groovy                |   2 +-
 .../MapSecondPassBinderSpec.groovy                 |   2 +-
 8 files changed, 240 insertions(+), 215 deletions(-)

diff --git 
a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/binder/CollectionBinder.java
 
b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/binder/CollectionBinder.java
index aca60c23da..42aa2d8b7a 100644
--- 
a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/binder/CollectionBinder.java
+++ 
b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/binder/CollectionBinder.java
@@ -104,7 +104,7 @@ public class CollectionBinder {
                 simpleValueColumnBinder
         );
         this.listSecondPassBinder = new 
ListSecondPassBinder(metadataBuildingContext, namingStrategy, 
collectionSecondPassBinder, simpleValueColumnBinder);
-        this.mapSecondPassBinder = new 
MapSecondPassBinder(metadataBuildingContext, namingStrategy, 
collectionSecondPassBinder);
+        this.mapSecondPassBinder = new 
MapSecondPassBinder(metadataBuildingContext, namingStrategy, 
collectionSecondPassBinder, simpleValueColumnBinder, new 
ColumnConfigToColumnBinder(), simpleValueColumnFetcher);
     }
 
     public CollectionBinder(MetadataBuildingContext metadataBuildingContext, 
PersistentEntityNamingStrategy namingStrategy, JdbcEnvironment jdbcEnvironment, 
CollectionHolder collectionHolder) {
diff --git 
a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/hibernate/HibernateToManyProperty.java
 
b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/hibernate/HibernateToManyProperty.java
index e6ae6becc5..766fe9942f 100644
--- 
a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/hibernate/HibernateToManyProperty.java
+++ 
b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/hibernate/HibernateToManyProperty.java
@@ -1,67 +1,19 @@
 package org.grails.orm.hibernate.cfg.domainbinding.hibernate;
 
-import org.grails.datastore.mapping.model.DatastoreConfigurationException;
-import org.grails.datastore.mapping.model.types.Association;
 import org.grails.datastore.mapping.model.types.Basic;
 import org.grails.datastore.mapping.model.types.mapping.PropertyWithMapping;
-import org.grails.orm.hibernate.cfg.GrailsHibernatePersistentEntity;
 import org.grails.orm.hibernate.cfg.GrailsHibernatePersistentProperty;
 import org.grails.orm.hibernate.cfg.PropertyConfig;
-import org.grails.orm.hibernate.cfg.domainbinding.util.OrderByClauseBuilder;
 import org.hibernate.FetchMode;
-import org.hibernate.MappingException;
-import org.hibernate.mapping.Collection;
-import org.hibernate.mapping.PersistentClass;
 import org.springframework.util.StringUtils;
 
 import java.util.Map;
-import java.util.Optional;
-import java.util.Set;
 
 /**
  * Marker interface for Hibernate to-many associations
  */
 public interface HibernateToManyProperty extends 
PropertyWithMapping<PropertyConfig>, GrailsHibernatePersistentProperty {
 
-    /**
-     * Binds the order by clause for the collection if configured.
-     *
-     * @param collection The Hibernate collection
-     * @param persistentClasses The map of persistent classes
-     * @param orderByClauseBuilder The order by clause builder
-     * @return The associated persistent class.
-     * @throws MappingException if the association references an unmapped class
-     */
-    default PersistentClass bindOrderBy(Collection collection, Map<?, ?> 
persistentClasses, OrderByClauseBuilder orderByClauseBuilder) {
-        return Optional.ofNullable(getHibernateAssociatedEntity())
-                .map(referenced -> {
-                    if (referenced.isTablePerHierarchySubclass()) {
-                        String discriminatorColumnName = 
referenced.getDiscriminatorColumnName();
-                        Set<String> discSet = 
referenced.buildDiscriminatorSet();
-                        String inclause = String.join(",", discSet);
-
-                        collection.setWhere(discriminatorColumnName + " in (" 
+ inclause + ")");
-                    }
-
-                    PersistentClass associatedClass = (PersistentClass) 
persistentClasses.get(referenced.getName());
-                    if (associatedClass == null) {
-                        throw new MappingException("Association references 
unmapped class: " + referenced.getName());
-                    }
-
-                    if (hasSort()) {
-                        if (!isBidirectional() && this instanceof 
HibernateOneToManyProperty) {
-                            throw new DatastoreConfigurationException("Default 
sort for associations [" + getHibernateOwner().getName() + "->" + getName() +
-                                    "] are not supported with unidirectional 
one to many relationships.");
-                        }
-                        GrailsHibernatePersistentProperty sortBy = 
(GrailsHibernatePersistentProperty) referenced.getPropertyByName(getSort());
-                        String order = 
Optional.ofNullable(getOrder()).orElse("asc");
-                        
collection.setOrderBy(orderByClauseBuilder.buildOrderByClause(sortBy.getName(), 
associatedClass, collection.getRole(), order));
-                    }
-                    return associatedClass;
-                })
-                .orElse(null);
-    }
-
     default boolean hasSort() {
         return StringUtils.hasText(getMappedForm().getSort());
     }
diff --git 
a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/CollectionSecondPassBinder.java
 
b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/CollectionSecondPassBinder.java
index 8b4f71022d..8ae242c7bb 100644
--- 
a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/CollectionSecondPassBinder.java
+++ 
b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/CollectionSecondPassBinder.java
@@ -1,6 +1,7 @@
 package org.grails.orm.hibernate.cfg.domainbinding.secondpass;
 
 import jakarta.annotation.Nonnull;
+import org.grails.datastore.mapping.model.DatastoreConfigurationException;
 import org.grails.datastore.mapping.model.config.GormProperties;
 import org.grails.datastore.mapping.model.types.Association;
 import org.grails.orm.hibernate.cfg.*;
@@ -13,6 +14,7 @@ import 
org.grails.orm.hibernate.cfg.domainbinding.binder.ManyToOneBinder;
 import org.grails.orm.hibernate.cfg.domainbinding.util.OrderByClauseBuilder;
 import 
org.grails.orm.hibernate.cfg.domainbinding.binder.SimpleValueColumnBinder;
 
+import org.hibernate.MappingException;
 import org.hibernate.boot.spi.InFlightMetadataCollector;
 import org.hibernate.mapping.*;
 import org.hibernate.mapping.Collection;
@@ -75,7 +77,7 @@ public class CollectionSecondPassBinder {
                                          Map<?, ?> persistentClasses,
                                          @Nonnull Collection collection) {
 
-        PersistentClass associatedClass = property.bindOrderBy(collection, 
persistentClasses, orderByClauseBuilder);
+        PersistentClass associatedClass = bindOrderBy(property, collection, 
persistentClasses);
 
         if (collection.isOneToMany()) {
             OneToMany oneToMany = (OneToMany) collection.getElement();
@@ -139,4 +141,34 @@ public class CollectionSecondPassBinder {
         }
         collectionKeyColumnUpdater.forceNullableAndCheckUpdatable(key, 
property);
     }
+
+    PersistentClass bindOrderBy(HibernateToManyProperty property, Collection 
collection, Map<?, ?> persistentClasses) {
+        return Optional.ofNullable(property.getHibernateAssociatedEntity())
+                .map(referenced -> {
+                    if (referenced.isTablePerHierarchySubclass()) {
+                        String discriminatorColumnName = 
referenced.getDiscriminatorColumnName();
+                        Set<String> discSet = 
referenced.buildDiscriminatorSet();
+                        String inclause = String.join(",", discSet);
+
+                        collection.setWhere(discriminatorColumnName + " in (" 
+ inclause + ")");
+                    }
+
+                    PersistentClass associatedClass = (PersistentClass) 
persistentClasses.get(referenced.getName());
+                    if (associatedClass == null) {
+                        throw new MappingException("Association references 
unmapped class: " + referenced.getName());
+                    }
+
+                    if (property.hasSort()) {
+                        if (!property.isBidirectional() && property instanceof 
HibernateOneToManyProperty) {
+                            throw new DatastoreConfigurationException("Default 
sort for associations [" + property.getHibernateOwner().getName() + "->" + 
property.getName() +
+                                    "] are not supported with unidirectional 
one to many relationships.");
+                        }
+                        GrailsHibernatePersistentProperty sortBy = 
(GrailsHibernatePersistentProperty) 
referenced.getPropertyByName(property.getSort());
+                        String order = 
Optional.ofNullable(property.getOrder()).orElse("asc");
+                        
collection.setOrderBy(orderByClauseBuilder.buildOrderByClause(sortBy.getName(), 
associatedClass, collection.getRole(), order));
+                    }
+                    return associatedClass;
+                })
+                .orElse(null);
+    }
 }
diff --git 
a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/MapSecondPassBinder.java
 
b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/MapSecondPassBinder.java
index 2acecfff91..12627d9cbb 100644
--- 
a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/MapSecondPassBinder.java
+++ 
b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/MapSecondPassBinder.java
@@ -34,11 +34,23 @@ public class MapSecondPassBinder {
     private final MetadataBuildingContext metadataBuildingContext;
     private final PersistentEntityNamingStrategy namingStrategy;
     private final CollectionSecondPassBinder collectionSecondPassBinder;
-
-    public MapSecondPassBinder(MetadataBuildingContext 
metadataBuildingContext, PersistentEntityNamingStrategy namingStrategy, 
CollectionSecondPassBinder collectionSecondPassBinder) {
+    private final SimpleValueColumnBinder simpleValueColumnBinder;
+    private final ColumnConfigToColumnBinder columnConfigToColumnBinder;
+    private final SimpleValueColumnFetcher simpleValueColumnFetcher;
+
+    public MapSecondPassBinder(
+            MetadataBuildingContext metadataBuildingContext,
+            PersistentEntityNamingStrategy namingStrategy,
+            CollectionSecondPassBinder collectionSecondPassBinder,
+            SimpleValueColumnBinder simpleValueColumnBinder,
+            ColumnConfigToColumnBinder columnConfigToColumnBinder,
+            SimpleValueColumnFetcher simpleValueColumnFetcher) {
         this.metadataBuildingContext = metadataBuildingContext;
         this.namingStrategy = namingStrategy;
         this.collectionSecondPassBinder = collectionSecondPassBinder;
+        this.simpleValueColumnBinder = simpleValueColumnBinder;
+        this.columnConfigToColumnBinder = columnConfigToColumnBinder;
+        this.simpleValueColumnFetcher = simpleValueColumnFetcher;
     }
 
     public void bindMapSecondPass(@Nonnull HibernateToManyProperty property, 
@Nonnull InFlightMetadataCollector mappings,
@@ -48,12 +60,12 @@ public class MapSecondPassBinder {
 
         String type = ((GrailsHibernatePersistentProperty) 
property).getIndexColumnType("string");
         String columnName1 = property.getIndexColumnName(namingStrategy);
-        new SimpleValueColumnBinder().bindSimpleValue(value, type, 
columnName1, true);
+        simpleValueColumnBinder.bindSimpleValue(value, type, columnName1, 
true);
         PropertyConfig mappedForm = property.getMappedForm();
         if (mappedForm.getIndexColumn() != null) {
-            Column column = new 
SimpleValueColumnFetcher().getColumnForSimpleValue(value);
+            Column column = 
simpleValueColumnFetcher.getColumnForSimpleValue(value);
             ColumnConfig columnConfig = 
getSingleColumnConfig(mappedForm.getIndexColumn());
-            new ColumnConfigToColumnBinder().bindColumnConfigToColumn(column, 
columnConfig, mappedForm);
+            columnConfigToColumnBinder.bindColumnConfigToColumn(column, 
columnConfig, mappedForm);
         }
 
         if (!value.isTypeSpecified()) {
@@ -78,7 +90,7 @@ public class MapSecondPassBinder {
                 typeName = StandardBasicTypes.STRING.getName();
             }
             String columnName = property.getMapElementName(namingStrategy);
-            new SimpleValueColumnBinder().bindSimpleValue(elt, typeName, 
columnName, false);
+            simpleValueColumnBinder.bindSimpleValue(elt, typeName, columnName, 
false);
 
             elt.setTypeName(typeName);
         }
diff --git 
a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/hibernate/HibernateToManyPropertySpec.groovy
 
b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/hibernate/HibernateToManyPropertySpec.groovy
deleted file mode 100644
index 656be6c97f..0000000000
--- 
a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/hibernate/HibernateToManyPropertySpec.groovy
+++ /dev/null
@@ -1,157 +0,0 @@
-package org.grails.orm.hibernate.cfg.domainbinding.hibernate
-
-import grails.gorm.annotation.Entity
-import grails.gorm.specs.HibernateGormDatastoreSpec
-import org.grails.orm.hibernate.cfg.CompositeIdentity
-import org.grails.orm.hibernate.cfg.Identity
-import org.grails.orm.hibernate.cfg.GrailsHibernatePersistentEntity
-import org.grails.orm.hibernate.cfg.GrailsHibernatePersistentProperty
-import org.grails.orm.hibernate.cfg.Mapping
-import org.grails.orm.hibernate.cfg.PropertyConfig
-import org.hibernate.mapping.Property
-import org.grails.datastore.mapping.model.PersistentEntity
-import org.grails.datastore.mapping.model.PropertyMapping
-import org.grails.datastore.mapping.reflect.EntityReflector
-import org.grails.datastore.mapping.model.types.Association
-import org.grails.datastore.mapping.model.types.Basic
-import org.grails.orm.hibernate.cfg.domainbinding.util.OrderByClauseBuilder
-import org.hibernate.mapping.Bag
-import org.hibernate.mapping.RootClass
-import org.grails.datastore.mapping.model.DatastoreConfigurationException
-
-import java.util.Optional
-import java.util.Map
-
-class HibernateToManyPropertySpec extends HibernateGormDatastoreSpec {
-
-    protected GrailsHibernatePersistentProperty 
createTestHibernateToManyProperty(Class<?> domainClass = TestEntityWithMany, 
String propertyName = "items") {
-        PersistentEntity entity = createPersistentEntity(domainClass)
-        GrailsHibernatePersistentProperty property = 
(GrailsHibernatePersistentProperty) entity.getPropertyByName(propertyName)
-        return property
-    }
-
-    def "test bindOrderBy with sort configured"() {
-        given:
-        def property = createTestHibernateToManyProperty(TestEntityWithMany, 
"items") as HibernateToManyProperty
-        def collection = new 
Bag(getGrailsDomainBinder().getMetadataBuildingContext(), null)
-        collection.setRole("TestEntityWithMany.items")
-        
-        def persistentClasses = [:]
-        def associatedPersistentClass = new 
RootClass(getGrailsDomainBinder().getMetadataBuildingContext())
-        associatedPersistentClass.setEntityName(AssociatedItem.name)
-        persistentClasses[AssociatedItem.name] = associatedPersistentClass
-        def orderByClauseBuilder = Mock(OrderByClauseBuilder)
-        
-        property.getMappedForm().setSort("value")
-        property.getMappedForm().setOrder("desc")
-
-        when:
-        def result = property.bindOrderBy(collection, persistentClasses, 
orderByClauseBuilder)
-
-        then:
-        1 * orderByClauseBuilder.buildOrderByClause("value", 
associatedPersistentClass, "TestEntityWithMany.items", "desc") >> "order by 
value desc"
-        collection.getOrderBy() == "order by value desc"
-        result == associatedPersistentClass
-    }
-
-    def "test bindOrderBy with unidirectional one-to-many throws exception"() {
-        given:
-        def property = createTestHibernateToManyProperty(UnidirectionalEntity, 
"items") as HibernateToManyProperty
-        def collection = new 
Bag(getGrailsDomainBinder().getMetadataBuildingContext(), null)
-        def persistentClasses = [:]
-        def associatedPersistentClass = new 
RootClass(getGrailsDomainBinder().getMetadataBuildingContext())
-        associatedPersistentClass.setEntityName(AssociatedItem.name)
-        persistentClasses[AssociatedItem.name] = associatedPersistentClass
-        def orderByClauseBuilder = Mock(OrderByClauseBuilder)
-        
-        property.getMappedForm().setSort("value")
-
-        when:
-        property.bindOrderBy(collection, persistentClasses, 
orderByClauseBuilder)
-
-        then:
-        thrown(DatastoreConfigurationException)
-    }
-
-    def "test bindOrderBy returns associatedClass even without sort"() {
-        given:
-        def property = createTestHibernateToManyProperty(TestEntityWithMany, 
"items") as HibernateToManyProperty
-        def collection = new 
Bag(getGrailsDomainBinder().getMetadataBuildingContext(), null)
-        def persistentClasses = [:]
-        def associatedPersistentClass = new 
RootClass(getGrailsDomainBinder().getMetadataBuildingContext())
-        associatedPersistentClass.setEntityName(AssociatedItem.name)
-        persistentClasses[AssociatedItem.name] = associatedPersistentClass
-        def orderByClauseBuilder = Mock(OrderByClauseBuilder)
-
-        when:
-        def result = property.bindOrderBy(collection, persistentClasses, 
orderByClauseBuilder)
-
-        then:
-        collection.getOrderBy() == null
-        result == associatedPersistentClass
-    }
-
-    def "test bindOrderBy throws MappingException when class is unmapped"() {
-        given:
-        def property = createTestHibernateToManyProperty(TestEntityWithMany, 
"items") as HibernateToManyProperty
-        def collection = new 
Bag(getGrailsDomainBinder().getMetadataBuildingContext(), null)
-        def persistentClasses = [:] // Empty map, so AssociatedItem will be 
missing
-        def orderByClauseBuilder = Mock(OrderByClauseBuilder)
-
-        when:
-        property.bindOrderBy(collection, persistentClasses, 
orderByClauseBuilder)
-
-        then:
-        thrown(org.hibernate.MappingException)
-    }
-
-    def "test bindOrderBy with table per hierarchy subclass"() {
-        given:
-        def property = createTestHibernateToManyProperty(TestEntityWithMany, 
"items") as HibernateToManyProperty
-        def collection = new 
Bag(getGrailsDomainBinder().getMetadataBuildingContext(), null)
-        def persistentClasses = [:]
-        def associatedPersistentClass = new 
RootClass(getGrailsDomainBinder().getMetadataBuildingContext())
-        associatedPersistentClass.setEntityName(AssociatedItem.name)
-        persistentClasses[AssociatedItem.name] = associatedPersistentClass
-        def orderByClauseBuilder = Mock(OrderByClauseBuilder)
-
-        // Mock GrailsHibernatePersistentEntity behavior for table per 
hierarchy
-        def referencedEntity = property.getHibernateAssociatedEntity()
-        def spiedReferencedEntity = Spy(referencedEntity)
-        spiedReferencedEntity.isTablePerHierarchySubclass() >> true
-        spiedReferencedEntity.getDiscriminatorColumnName() >> "item_type"
-        spiedReferencedEntity.buildDiscriminatorSet() >> (["'A'", "'B'"] as 
Set)
-
-        // Inject the spy if possible, or mock the getter on property
-        def spiedProperty = Spy(property)
-        spiedProperty.getHibernateAssociatedEntity() >> spiedReferencedEntity
-
-        when:
-        spiedProperty.bindOrderBy(collection, persistentClasses, 
orderByClauseBuilder)
-
-        then:
-        collection.getWhere() == "item_type in ('A','B')"
-    }
-}
-
-@Entity
-class TestEntityWithMany {
-    Long id
-    String name
-    static hasMany = [items: AssociatedItem]
-}
-
-@Entity
-class AssociatedItem {
-    Long id
-    String value
-    TestEntityWithMany parent // Bidirectional for association property testing
-    static belongsTo = [parent: TestEntityWithMany]
-}
-
-@Entity
-class UnidirectionalEntity {
-    Long id
-    Set<AssociatedItem> items
-    static hasMany = [items: AssociatedItem]
-}
diff --git 
a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/CollectionSecondPassBinderSpec.groovy
 
b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/CollectionSecondPassBinderSpec.groovy
new file mode 100644
index 0000000000..80bd03af2a
--- /dev/null
+++ 
b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/CollectionSecondPassBinderSpec.groovy
@@ -0,0 +1,186 @@
+package org.grails.orm.hibernate.cfg.domainbinding.secondpass
+
+import grails.gorm.annotation.Entity
+import grails.gorm.specs.HibernateGormDatastoreSpec
+import org.grails.orm.hibernate.cfg.GrailsHibernatePersistentEntity
+import org.grails.orm.hibernate.cfg.GrailsHibernatePersistentProperty
+import 
org.grails.orm.hibernate.cfg.domainbinding.hibernate.HibernateToManyProperty
+import 
org.grails.orm.hibernate.cfg.domainbinding.hibernate.HibernateOneToManyProperty
+import org.grails.orm.hibernate.cfg.domainbinding.util.OrderByClauseBuilder
+import org.hibernate.mapping.Bag
+import org.hibernate.mapping.RootClass
+import org.hibernate.mapping.PersistentClass
+import org.grails.datastore.mapping.model.DatastoreConfigurationException
+import org.grails.datastore.mapping.model.PersistentEntity
+import org.grails.orm.hibernate.cfg.domainbinding.binder.ManyToOneBinder
+import org.grails.orm.hibernate.cfg.domainbinding.binder.SimpleValueBinder
+import org.grails.orm.hibernate.cfg.domainbinding.binder.ManyToOneValuesBinder
+import 
org.grails.orm.hibernate.cfg.domainbinding.binder.CompositeIdentifierToManyToOneBinder
+import org.grails.orm.hibernate.cfg.domainbinding.util.SimpleValueColumnFetcher
+import 
org.grails.orm.hibernate.cfg.domainbinding.binder.CollectionForPropertyConfigBinder
+import org.grails.orm.hibernate.cfg.domainbinding.util.DefaultColumnNameFetcher
+import 
org.grails.orm.hibernate.cfg.domainbinding.binder.SimpleValueColumnBinder
+import org.grails.orm.hibernate.cfg.domainbinding.util.BackticksRemover
+
+import java.util.Optional
+import java.util.Map
+
+class CollectionSecondPassBinderSpec extends HibernateGormDatastoreSpec {
+
+    CollectionSecondPassBinder binder
+
+    void setup() {
+        def gdb = getGrailsDomainBinder()
+        def mbc = gdb.getMetadataBuildingContext()
+        def ns = gdb.getNamingStrategy()
+        def je = gdb.getJdbcEnvironment()
+        def svb = new SimpleValueBinder(mbc, ns, je)
+        def svcf = new SimpleValueColumnFetcher()
+        def citmto = new CompositeIdentifierToManyToOneBinder(mbc, ns, je)
+        def mtob = new ManyToOneBinder(mbc, ns, svb, new 
ManyToOneValuesBinder(), citmto, svcf)
+        def pkvc = new PrimaryKeyValueCreator(mbc)
+        def cku = new CollectionKeyColumnUpdater()
+        def botml = new BidirectionalOneToManyLinker(new 
org.grails.orm.hibernate.cfg.domainbinding.util.GrailsPropertyResolver())
+        def dkvb = new DependentKeyValueBinder(svb, citmto)
+        def cwjtb = new CollectionWithJoinTableBinder(mbc, ns, new 
UnidirectionalOneToManyInverseValuesBinder(), null, citmto, svcf, new 
CollectionForPropertyConfigBinder(), new SimpleValueColumnBinder(), null)
+        def uotmb = new UnidirectionalOneToManyBinder(cwjtb)
+        def cfpcb = new CollectionForPropertyConfigBinder()
+        def dcnf = new DefaultColumnNameFetcher(ns, new BackticksRemover())
+        def svcb = new SimpleValueColumnBinder()
+
+        binder = new CollectionSecondPassBinder(mtob, pkvc, cku, botml, dkvb, 
uotmb, cwjtb, cfpcb, dcnf, svcb)
+    }
+
+    protected GrailsHibernatePersistentProperty 
createTestHibernateToManyProperty(Class<?> domainClass = 
CSPBTestEntityWithMany, String propertyName = "items") {
+        PersistentEntity entity = createPersistentEntity(domainClass)
+        GrailsHibernatePersistentProperty property = 
(GrailsHibernatePersistentProperty) entity.getPropertyByName(propertyName)
+        return property
+    }
+
+    def "test bindOrderBy with sort configured"() {
+        given:
+        def property = 
createTestHibernateToManyProperty(CSPBTestEntityWithMany, "items") as 
HibernateToManyProperty
+        def collection = new 
Bag(getGrailsDomainBinder().getMetadataBuildingContext(), null)
+        collection.setRole("CSPBTestEntityWithMany.items")
+        
+        def persistentClasses = [:]
+        def associatedPersistentClass = new 
RootClass(getGrailsDomainBinder().getMetadataBuildingContext())
+        associatedPersistentClass.setEntityName(CSPBAssociatedItem.name)
+        def valueProperty = new org.hibernate.mapping.Property()
+        valueProperty.setName("value")
+        def simpleValue = new 
org.hibernate.mapping.BasicValue(getGrailsDomainBinder().getMetadataBuildingContext(),
 new org.hibernate.mapping.Table("ASSOCIATED_ITEM"))
+        simpleValue.setTypeName("string")
+        def column = new org.hibernate.mapping.Column("VALUE")
+        simpleValue.addColumn(column)
+        valueProperty.setValue(simpleValue)
+        associatedPersistentClass.addProperty(valueProperty)
+        persistentClasses[CSPBAssociatedItem.name] = associatedPersistentClass
+        
+        property.getMappedForm().setSort("value")
+        property.getMappedForm().setOrder("desc")
+
+        when:
+        def result = binder.bindOrderBy(property, collection, 
persistentClasses)
+
+        then:
+        collection.getOrderBy() != null
+        result == associatedPersistentClass
+    }
+
+    def "test bindOrderBy with unidirectional one-to-many throws exception"() {
+        given:
+        def property = 
createTestHibernateToManyProperty(CSPBUnidirectionalEntity, "items") as 
HibernateToManyProperty
+        def collection = new 
Bag(getGrailsDomainBinder().getMetadataBuildingContext(), null)
+        def persistentClasses = [:]
+        def associatedPersistentClass = new 
RootClass(getGrailsDomainBinder().getMetadataBuildingContext())
+        associatedPersistentClass.setEntityName(CSPBAssociatedItem.name)
+        persistentClasses[CSPBAssociatedItem.name] = associatedPersistentClass
+        
+        property.getMappedForm().setSort("value")
+
+        when:
+        binder.bindOrderBy(property, collection, persistentClasses)
+
+        then:
+        thrown(DatastoreConfigurationException)
+    }
+
+    def "test bindOrderBy returns associatedClass even without sort"() {
+        given:
+        def property = 
createTestHibernateToManyProperty(CSPBTestEntityWithMany, "items") as 
HibernateToManyProperty
+        def collection = new 
Bag(getGrailsDomainBinder().getMetadataBuildingContext(), null)
+        def persistentClasses = [:]
+        def associatedPersistentClass = new 
RootClass(getGrailsDomainBinder().getMetadataBuildingContext())
+        associatedPersistentClass.setEntityName(CSPBAssociatedItem.name)
+        persistentClasses[CSPBAssociatedItem.name] = associatedPersistentClass
+
+        when:
+        def result = binder.bindOrderBy(property, collection, 
persistentClasses)
+
+        then:
+        collection.getOrderBy() == null
+        result == associatedPersistentClass
+    }
+
+    def "test bindOrderBy throws MappingException when class is unmapped"() {
+        given:
+        def property = 
createTestHibernateToManyProperty(CSPBTestEntityWithMany, "items") as 
HibernateToManyProperty
+        def collection = new 
Bag(getGrailsDomainBinder().getMetadataBuildingContext(), null)
+        def persistentClasses = [:] // Empty map, so CSPBAssociatedItem will 
be missing
+
+        when:
+        binder.bindOrderBy(property, collection, persistentClasses)
+
+        then:
+        thrown(org.hibernate.MappingException)
+    }
+
+    def "test bindOrderBy with table per hierarchy subclass"() {
+        given:
+        def property = 
createTestHibernateToManyProperty(CSPBTestEntityWithMany, "items") as 
HibernateToManyProperty
+        def collection = new 
Bag(getGrailsDomainBinder().getMetadataBuildingContext(), null)
+        def persistentClasses = [:]
+        def associatedPersistentClass = new 
RootClass(getGrailsDomainBinder().getMetadataBuildingContext())
+        associatedPersistentClass.setEntityName(CSPBAssociatedItem.name)
+        persistentClasses[CSPBAssociatedItem.name] = associatedPersistentClass
+
+        // Mock GrailsHibernatePersistentEntity behavior for table per 
hierarchy
+        def referencedEntity = property.getHibernateAssociatedEntity()
+        def spiedReferencedEntity = Spy(referencedEntity)
+        spiedReferencedEntity.isTablePerHierarchySubclass() >> true
+        spiedReferencedEntity.getDiscriminatorColumnName() >> "item_type"
+        spiedReferencedEntity.buildDiscriminatorSet() >> (["'A'", "'B'"] as 
Set)
+
+        // Inject the spy if possible, or mock the getter on property
+        def spiedProperty = Spy(property)
+        spiedProperty.getHibernateAssociatedEntity() >> spiedReferencedEntity
+
+        when:
+        binder.bindOrderBy(spiedProperty, collection, persistentClasses)
+
+        then:
+        collection.getWhere() == "item_type in ('A','B')"
+    }
+}
+
+@Entity
+class CSPBTestEntityWithMany {
+    Long id
+    String name
+    static hasMany = [items: CSPBAssociatedItem]
+}
+
+@Entity
+class CSPBAssociatedItem {
+    Long id
+    String value
+    CSPBTestEntityWithMany parent // Bidirectional for association property 
testing
+    static belongsTo = [parent: CSPBTestEntityWithMany]
+}
+
+@Entity
+class CSPBUnidirectionalEntity {
+    Long id
+    Set<CSPBAssociatedItem> items
+    static hasMany = [items: CSPBAssociatedItem]
+}
diff --git 
a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/ListSecondPassBinderSpec.groovy
 
b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/ListSecondPassBinderSpec.groovy
similarity index 99%
rename from 
grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/ListSecondPassBinderSpec.groovy
rename to 
grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/ListSecondPassBinderSpec.groovy
index f6f6df3c9e..0f50cfea35 100644
--- 
a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/ListSecondPassBinderSpec.groovy
+++ 
b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/ListSecondPassBinderSpec.groovy
@@ -1,4 +1,4 @@
-package org.grails.orm.hibernate.cfg.domainbinding
+package org.grails.orm.hibernate.cfg.domainbinding.secondpass
 
 import grails.gorm.specs.HibernateGormDatastoreSpec
 import org.grails.orm.hibernate.cfg.GrailsHibernatePersistentEntity
diff --git 
a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/MapSecondPassBinderSpec.groovy
 
b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/MapSecondPassBinderSpec.groovy
similarity index 99%
rename from 
grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/MapSecondPassBinderSpec.groovy
rename to 
grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/MapSecondPassBinderSpec.groovy
index 054430be5c..08eca99a57 100644
--- 
a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/MapSecondPassBinderSpec.groovy
+++ 
b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/MapSecondPassBinderSpec.groovy
@@ -1,4 +1,4 @@
-package org.grails.orm.hibernate.cfg.domainbinding
+package org.grails.orm.hibernate.cfg.domainbinding.secondpass
 
 import grails.gorm.specs.HibernateGormDatastoreSpec
 import org.grails.orm.hibernate.cfg.GrailsHibernatePersistentEntity

Reply via email to