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
