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 ac444f3f3a7e3ee62070936e70cb6c8d6fe211b8
Author: Walter Duque de Estrada <[email protected]>
AuthorDate: Tue Feb 17 10:29:12 2026 -0600

    Refactor CollectionKeyColumn handling
---
 .../cfg/domainbinding/binder/CollectionBinder.java |   4 +-
 .../secondpass/CollectionKeyColumnConfigurer.java  |  33 +++++++
 .../secondpass/CollectionKeyColumnUpdater.java     |  29 ++++++
 .../secondpass/CollectionSecondPassBinder.java     |  31 ++-----
 .../CollectionKeyColumnConfigurerSpec.groovy       | 100 +++++++++++++++++++++
 .../CollectionKeyColumnUpdaterSpec.groovy          |  67 ++++++++++++++
 6 files changed, 237 insertions(+), 27 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 4f451714e0..1217a0d03c 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
@@ -9,6 +9,7 @@ import 
org.grails.orm.hibernate.cfg.domainbinding.hibernate.HibernateToManyPrope
 import org.grails.orm.hibernate.cfg.PersistentEntityNamingStrategy;
 import org.grails.orm.hibernate.cfg.PropertyConfig;
 import org.grails.orm.hibernate.cfg.JoinTable;
+import 
org.grails.orm.hibernate.cfg.domainbinding.secondpass.CollectionKeyColumnUpdater;
 import org.grails.orm.hibernate.cfg.domainbinding.util.CascadeBehavior;
 import org.grails.orm.hibernate.cfg.domainbinding.util.BackticksRemover;
 import 
org.grails.orm.hibernate.cfg.domainbinding.util.ColumnNameForPropertyAndPathFetcher;
@@ -76,7 +77,8 @@ public class CollectionBinder {
                 manyToOneBinder,
                 compositeIdentifierToManyToOneBinder,
                 simpleValueColumnFetcher,
-                new PrimaryKeyValueCreator(metadataBuildingContext)
+                new PrimaryKeyValueCreator(metadataBuildingContext),
+                new CollectionKeyColumnUpdater()
         );
         this.listSecondPassBinder = new 
ListSecondPassBinder(metadataBuildingContext, namingStrategy, 
collectionSecondPassBinder);
         this.mapSecondPassBinder = new 
MapSecondPassBinder(metadataBuildingContext, namingStrategy, 
collectionSecondPassBinder);
diff --git 
a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/CollectionKeyColumnConfigurer.java
 
b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/CollectionKeyColumnConfigurer.java
new file mode 100644
index 0000000000..76f83d8068
--- /dev/null
+++ 
b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/CollectionKeyColumnConfigurer.java
@@ -0,0 +1,33 @@
+package org.grails.orm.hibernate.cfg.domainbinding.secondpass;
+
+import org.grails.datastore.mapping.model.types.Association;
+import org.grails.orm.hibernate.cfg.GrailsHibernatePersistentEntity;
+import org.grails.orm.hibernate.cfg.GrailsHibernatePersistentProperty;
+import org.hibernate.mapping.Column;
+import org.hibernate.mapping.DependantValue;
+
+import java.util.Iterator;
+import java.util.stream.StreamSupport;
+
+/**
+ * Configures nullability and updateability for collection key columns.
+ */
+public class CollectionKeyColumnConfigurer {
+
+    public void configure(DependantValue key, 
GrailsHibernatePersistentProperty property) {
+        // Force all columns in the key to be nullable
+        StreamSupport.stream(key.getColumns().spliterator(), false)
+                .filter(Column.class::isInstance)
+                .map(Column.class::cast)
+                .forEach(column -> column.setNullable(true));
+
+        // Determine updateable status based on unidirectional associations
+        long unidirectionalCount = property.getHibernateOwner()
+                .getPersistentPropertiesToBind()
+                .stream()
+                .filter(p -> p instanceof Association association && 
!association.isBidirectional())
+                .count();
+
+        key.setUpdateable(unidirectionalCount <= 1);
+    }
+}
diff --git 
a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/CollectionKeyColumnUpdater.java
 
b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/CollectionKeyColumnUpdater.java
new file mode 100644
index 0000000000..d0045fdfac
--- /dev/null
+++ 
b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/CollectionKeyColumnUpdater.java
@@ -0,0 +1,29 @@
+package org.grails.orm.hibernate.cfg.domainbinding.secondpass;
+
+import org.grails.datastore.mapping.model.types.Association;
+import org.grails.orm.hibernate.cfg.GrailsHibernatePersistentProperty;
+import org.hibernate.mapping.Column;
+import org.hibernate.mapping.DependantValue;
+
+import java.util.stream.StreamSupport;
+
+/**
+ * Forces columns to be nullable and checks if the key is updateable.
+ */
+public class CollectionKeyColumnUpdater {
+
+    public void forceNullableAndCheckUpdateable(DependantValue key, 
GrailsHibernatePersistentProperty property) {
+        StreamSupport.stream(key.getColumns().spliterator(), false)
+                .filter(Column.class::isInstance)
+                .map(Column.class::cast)
+                .forEach(column -> column.setNullable(true));
+
+        long unidirectionalCount = property.getHibernateOwner()
+                .getPersistentPropertiesToBind()
+                .stream()
+                .filter(p -> p instanceof Association association && 
!association.isBidirectional())
+                .count();
+
+        key.setUpdateable(unidirectionalCount <= 1);
+    }
+}
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 5f165a7b49..bef359b57d 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
@@ -57,6 +57,7 @@ public class CollectionSecondPassBinder {
     private final CompositeIdentifierToManyToOneBinder 
compositeIdentifierToManyToOneBinder;
     private final SimpleValueColumnFetcher simpleValueColumnFetcher;
     private final PrimaryKeyValueCreator primaryKeyValueCreator;
+    private final CollectionKeyColumnUpdater collectionKeyColumnUpdater;
 
     public CollectionSecondPassBinder(
             MetadataBuildingContext metadataBuildingContext,
@@ -67,7 +68,8 @@ public class CollectionSecondPassBinder {
             ManyToOneBinder manyToOneBinder,
             CompositeIdentifierToManyToOneBinder 
compositeIdentifierToManyToOneBinder,
             SimpleValueColumnFetcher simpleValueColumnFetcher,
-            PrimaryKeyValueCreator primaryKeyValueCreator) {
+            PrimaryKeyValueCreator primaryKeyValueCreator,
+            CollectionKeyColumnUpdater collectionKeyColumnUpdater) {
         this.metadataBuildingContext = metadataBuildingContext;
         this.namingStrategy = namingStrategy;
         this.jdbcEnvironment = jdbcEnvironment;
@@ -77,6 +79,7 @@ public class CollectionSecondPassBinder {
         this.compositeIdentifierToManyToOneBinder = 
compositeIdentifierToManyToOneBinder;
         this.simpleValueColumnFetcher = simpleValueColumnFetcher;
         this.primaryKeyValueCreator = primaryKeyValueCreator;
+        this.collectionKeyColumnUpdater = collectionKeyColumnUpdater;
         this.defaultColumnNameFetcher = new 
DefaultColumnNameFetcher(namingStrategy);
         this.orderByClauseBuilder = new OrderByClauseBuilder();
     }
@@ -230,7 +233,7 @@ public class CollectionSecondPassBinder {
         } else if (property.supportsJoinColumnMapping()) {
             bindCollectionWithJoinTable(property, mappings, collection, 
propConfig);
         }
-        forceNullableAndCheckUpdateable(key, property);
+        collectionKeyColumnUpdater.forceNullableAndCheckUpdateable(key, 
property); // Use the injected service
     }
 
     private void bindUnidirectionalOneToMany(HibernateOneToManyProperty 
property, @Nonnull InFlightMetadataCollector mappings, Collection collection) {
@@ -421,28 +424,4 @@ public class CollectionSecondPassBinder {
             throw e;
         }
     }
-
-    private void forceNullableAndCheckUpdateable(DependantValue key, 
GrailsHibernatePersistentProperty property) {
-        Iterator<?> it = key.getColumns().iterator();
-        while (it.hasNext()) {
-            Object next = it.next();
-            if (next instanceof Column) {
-                ((Column) next).setNullable(true);
-            }
-        }
-        
-        int unidirectionalCount = 0;
-        GrailsHibernatePersistentEntity owner = property.getHibernateOwner();
-        for (GrailsHibernatePersistentProperty p : 
owner.getPersistentPropertiesToBind()) {
-            if (p instanceof Association association && 
!association.isBidirectional()) {
-                unidirectionalCount++;
-            }
-        }
-        
-        if (unidirectionalCount > 1) {
-            key.setUpdateable(false);
-        } else {
-            key.setUpdateable(true);
-        }
-    }
 }
diff --git 
a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/CollectionKeyColumnConfigurerSpec.groovy
 
b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/CollectionKeyColumnConfigurerSpec.groovy
new file mode 100644
index 0000000000..7b612d8598
--- /dev/null
+++ 
b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/CollectionKeyColumnConfigurerSpec.groovy
@@ -0,0 +1,100 @@
+package org.grails.orm.hibernate.cfg.domainbinding.secondpass
+
+import grails.gorm.annotation.Entity
+import grails.gorm.specs.HibernateGormDatastoreSpec
+import org.grails.orm.hibernate.cfg.GrailsHibernatePersistentProperty
+import org.hibernate.mapping.Column
+import org.hibernate.mapping.DependantValue
+import spock.lang.Subject
+
+class CollectionKeyColumnConfigurerSpec extends HibernateGormDatastoreSpec {
+
+    @Subject
+    CollectionKeyColumnConfigurer configurer = new 
CollectionKeyColumnConfigurer()
+
+    void "test configure with single unidirectional association"() {
+        given:
+        def owner = 
createPersistentEntity(CollectionKeyColumnConfigurerSpecParent)
+        def property = (GrailsHibernatePersistentProperty) 
owner.getPropertyByName("children")
+        
+        Column column1 = new Column("keyCol1")
+        Column column2 = new Column("keyCol2")
+        
+        DependantValue key = Mock(DependantValue)
+        key.getColumns() >> [column1, column2]
+
+        when:
+        configurer.configure(key, property)
+
+        then:
+        column1.isNullable()
+        column2.isNullable()
+        1 * key.setUpdateable(true)
+    }
+
+    void "test configure with multiple unidirectional associations"() {
+        given:
+        def owner = 
createPersistentEntity(CollectionKeyColumnConfigurerSpecMultiParent)
+        def property = (GrailsHibernatePersistentProperty) 
owner.getPropertyByName("children1")
+        
+        Column column1 = new Column("keyCol1")
+        
+        DependantValue key = Mock(DependantValue)
+        key.getColumns() >> [column1]
+
+        when:
+        configurer.configure(key, property)
+
+        then:
+        column1.isNullable()
+        1 * key.setUpdateable(false)
+    }
+
+    void "test configure with bidirectional association"() {
+        given:
+        def owner = 
createPersistentEntity(CollectionKeyColumnConfigurerSpecBiParent)
+        def property = (GrailsHibernatePersistentProperty) 
owner.getPropertyByName("children")
+        
+        Column column = new Column("keyCol")
+        
+        DependantValue key = Mock(DependantValue)
+        key.getColumns() >> [column]
+
+        when:
+        configurer.configure(key, property)
+
+        then:
+        column.isNullable()
+        1 * key.setUpdateable(true)
+    }
+}
+
+@Entity
+class CollectionKeyColumnConfigurerSpecParent {
+    Long id
+    static hasMany = [children: CollectionKeyColumnConfigurerSpecChild]
+}
+
+@Entity
+class CollectionKeyColumnConfigurerSpecChild {
+    Long id
+}
+
+@Entity
+class CollectionKeyColumnConfigurerSpecMultiParent {
+    Long id
+    static hasMany = [children1: CollectionKeyColumnConfigurerSpecChild, 
children2: CollectionKeyColumnConfigurerSpecChild]
+}
+
+@Entity
+class CollectionKeyColumnConfigurerSpecBiParent {
+    Long id
+    static hasMany = [children: CollectionKeyColumnConfigurerSpecBiChild]
+}
+
+@Entity
+class CollectionKeyColumnConfigurerSpecBiChild {
+    Long id
+    CollectionKeyColumnConfigurerSpecBiParent parent
+    static belongsTo = [parent: CollectionKeyColumnConfigurerSpecBiParent]
+}
diff --git 
a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/CollectionKeyColumnUpdaterSpec.groovy
 
b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/CollectionKeyColumnUpdaterSpec.groovy
new file mode 100644
index 0000000000..16f8905f54
--- /dev/null
+++ 
b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/secondpass/CollectionKeyColumnUpdaterSpec.groovy
@@ -0,0 +1,67 @@
+package org.grails.orm.hibernate.cfg.domainbinding.secondpass
+
+import grails.gorm.annotation.Entity
+import grails.gorm.specs.HibernateGormDatastoreSpec
+import org.grails.orm.hibernate.cfg.GrailsHibernatePersistentProperty
+import org.hibernate.mapping.Column
+import org.hibernate.mapping.DependantValue
+import spock.lang.Subject
+
+class CollectionKeyColumnUpdaterSpec extends HibernateGormDatastoreSpec {
+
+    @Subject
+    CollectionKeyColumnUpdater updater = new CollectionKeyColumnUpdater()
+
+    void "test forceNullableAndCheckUpdateable with single unidirectional 
association"() {
+        given:
+        def owner = 
createPersistentEntity(CollectionKeyColumnUpdaterSpecParent)
+        def property = (GrailsHibernatePersistentProperty) 
owner.getPropertyByName("children")
+        
+        Column column = new Column("test")
+        column.setNullable(false)
+        
+        DependantValue key = Mock(DependantValue)
+        key.getColumns() >> [column]
+
+        when:
+        updater.forceNullableAndCheckUpdateable(key, property)
+
+        then:
+        column.isNullable()
+        1 * key.setUpdateable(true)
+    }
+
+    void "test forceNullableAndCheckUpdateable with multiple unidirectional 
associations"() {
+        given:
+        def owner = 
createPersistentEntity(CollectionKeyColumnUpdaterSpecMultiParent)
+        def property = (GrailsHibernatePersistentProperty) 
owner.getPropertyByName("children1")
+        
+        Column column = new Column("test")
+        
+        DependantValue key = Mock(DependantValue)
+        key.getColumns() >> [column]
+
+        when:
+        updater.forceNullableAndCheckUpdateable(key, property)
+
+        then:
+        1 * key.setUpdateable(false)
+    }
+}
+
+@Entity
+class CollectionKeyColumnUpdaterSpecParent {
+    Long id
+    static hasMany = [children: CollectionKeyColumnUpdaterSpecChild]
+}
+
+@Entity
+class CollectionKeyColumnUpdaterSpecChild {
+    Long id
+}
+
+@Entity
+class CollectionKeyColumnUpdaterSpecMultiParent {
+    Long id
+    static hasMany = [children1: CollectionKeyColumnUpdaterSpecChild, 
children2: CollectionKeyColumnUpdaterSpecChild]
+}

Reply via email to