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] +}
