This is an automated email from the ASF dual-hosted git repository. borinquenkid pushed a commit to branch merge-hibernate6 in repository https://gitbox.apache.org/repos/asf/grails-core.git
commit 290bfad3dd38961193f28ab1e6fa6d754009c67d Author: Walter Duque de Estrada <[email protected]> AuthorDate: Sun Jul 20 18:08:49 2025 -0500 EnumTypeBinder and other classes --- .../hibernate/cfg/AbstractGrailsDomainBinder.java | 1 + .../orm/hibernate/cfg/GrailsDomainBinder.java | 42 ++-- .../hibernate/cfg/HibernatePersistentEntity.java | 6 + .../domainbinding/ColumnConfigToColumnBinder.java | 7 +- .../cfg/domainbinding/EnumTypeBinder.java | 98 ++++++++ .../hibernate/cfg/domainbinding/IndexBinder.java | 8 +- .../PersistentPropertyToPropertyConfig.java | 3 +- .../cfg/domainbinding/TypeNameProvider.java | 5 +- .../gorm/specs/HibernateGormDatastoreSpec.groovy | 4 + .../ColumnConfigToColumnBinderSpec.groovy | 8 - .../cfg/domainbinding/EnumTypeBinderSpec.groovy | 249 +++++++++++++++++++++ .../cfg/domainbinding/IndexBinderSpec.groovy | 7 - .../datastore/mapping/model/PersistentEntity.java | 6 +- 13 files changed, 400 insertions(+), 44 deletions(-) diff --git a/grails-data-hibernate6/core/src/main/groovy/org/grails/orm/hibernate/cfg/AbstractGrailsDomainBinder.java b/grails-data-hibernate6/core/src/main/groovy/org/grails/orm/hibernate/cfg/AbstractGrailsDomainBinder.java index 264fe90497..2fea30c3a3 100644 --- a/grails-data-hibernate6/core/src/main/groovy/org/grails/orm/hibernate/cfg/AbstractGrailsDomainBinder.java +++ b/grails-data-hibernate6/core/src/main/groovy/org/grails/orm/hibernate/cfg/AbstractGrailsDomainBinder.java @@ -61,6 +61,7 @@ public abstract class AbstractGrailsDomainBinder { return domainClass == null ? null : MAPPING_CACHE.get(domainClass.getJavaClass()); } + public static void clearMappingCache() { MAPPING_CACHE.clear(); } diff --git a/grails-data-hibernate6/core/src/main/groovy/org/grails/orm/hibernate/cfg/GrailsDomainBinder.java b/grails-data-hibernate6/core/src/main/groovy/org/grails/orm/hibernate/cfg/GrailsDomainBinder.java index aef1092b71..9038bfc537 100644 --- a/grails-data-hibernate6/core/src/main/groovy/org/grails/orm/hibernate/cfg/GrailsDomainBinder.java +++ b/grails-data-hibernate6/core/src/main/groovy/org/grails/orm/hibernate/cfg/GrailsDomainBinder.java @@ -299,8 +299,8 @@ public class GrailsDomainBinder implements MetadataContributor { SimpleValue elt = new BasicValue(metadataBuildingContext, map.getCollectionTable()); map.setElement(elt); - PersistentEntity owner = property.getOwner(); - Mapping mapping = getMapping(owner); + HibernatePersistentEntity owner = (HibernatePersistentEntity) property.getOwner(); + Mapping mapping =owner.getMappedForm(); String typeName = new TypeNameProvider().getTypeName(property, mapping); if (typeName == null ) { @@ -704,15 +704,12 @@ public class GrailsDomainBinder implements MetadataContributor { } private Mapping getRootMapping(PersistentEntity referenced) { - if (referenced == null) return null; - Class<?> current = referenced.getJavaClass(); - while (true) { - Class<?> superClass = current.getSuperclass(); - if (Object.class.equals(superClass)) break; - current = superClass; - } - - return getMapping(current); + return Optional.of(referenced) + .map(PersistentEntity::getRootEntity) + .filter(HibernatePersistentEntity.class::isInstance) + .map(HibernatePersistentEntity.class::cast) + .map(HibernatePersistentEntity::getMappedForm) + .orElse(null); } private boolean isBidirectionalOneToManyMap(Association property) { @@ -740,7 +737,7 @@ public class GrailsDomainBinder implements MetadataContributor { var joinColumnMappingOptional = Optional.ofNullable(config).map(PropertyConfig::getJoinTableColumnConfig); if (isBasicCollectionType) { - final Class<?> referencedType = ((Basic)property).getComponentType(); + final Class<?> referencedType = property.getType(); String className = referencedType.getName(); final boolean isEnum = referencedType.isEnum(); if (joinColumnMappingOptional.isPresent()) { @@ -1360,7 +1357,7 @@ public class GrailsDomainBinder implements MetadataContributor { root.setPolymorphic(false); } else { root.setPolymorphic(true); - Mapping m = getMapping(entity); + Mapping m =entity.getMappedForm(); boolean tablePerSubclass = m != null && !m.getTablePerHierarchy(); if (!tablePerSubclass) { // if the root class has children create a discriminator property @@ -1689,7 +1686,7 @@ public class GrailsDomainBinder implements MetadataContributor { RootClass root, InFlightMetadataCollector mappings, String sessionFactoryBeanName) { // get the schema and catalog names from the configuration - Mapping m = ofNullable(getMapping(domainClass.getJavaClass())).orElseThrow(); + Mapping m = domainClass.getMappedForm(); configureDerivedProperties(domainClass, m); CacheConfig cc = m.getCache(); @@ -1812,7 +1809,7 @@ public class GrailsDomainBinder implements MetadataContributor { final List<PersistentProperty> persistentProperties = domainClass.getPersistentProperties(); Table table = persistentClass.getTable(); - Mapping gormMapping = domainClass.getMapping().getMappedForm(); + Mapping gormMapping = domainClass.getMappedForm(); if (gormMapping != null) { table.setComment(gormMapping.getComment()); @@ -2149,8 +2146,8 @@ public class GrailsDomainBinder implements MetadataContributor { private boolean isComponentPropertyNullable(PersistentProperty componentProperty) { if (componentProperty == null) return false; - final PersistentEntity domainClass = componentProperty.getOwner(); - final Mapping mapping = getMapping(domainClass.getJavaClass()); + final HibernatePersistentEntity domainClass = (HibernatePersistentEntity) componentProperty.getOwner(); + final Mapping mapping = domainClass.getMappedForm(); return !domainClass.isRoot() && (mapping == null || mapping.isTablePerHierarchy()) || componentProperty.isNullable(); } @@ -2189,7 +2186,7 @@ public class GrailsDomainBinder implements MetadataContributor { bindManyToOneValues(property, manyToOne); PersistentEntity refDomainClass = property instanceof ManyToMany ? property.getOwner() : property.getAssociatedEntity(); - Mapping mapping = getMapping(refDomainClass); + Mapping mapping = ((HibernatePersistentEntity)refDomainClass).getMappedForm(); boolean isComposite = hasCompositeIdentifier(mapping); if (isComposite) { CompositeIdentity ci = (CompositeIdentity) mapping.getIdentity(); @@ -2416,7 +2413,7 @@ public class GrailsDomainBinder implements MetadataContributor { private void bindSimpleId(PersistentProperty identifier, RootClass entity, InFlightMetadataCollector mappings, Identity mappedId, String sessionFactoryBeanName) { - Mapping mapping = getMapping(identifier.getOwner()); + Mapping mapping = ((HibernatePersistentEntity)identifier.getOwner()).getMappedForm(); boolean useSequence = mapping != null && mapping.isTablePerConcreteClass(); // create the id value @@ -2620,7 +2617,7 @@ public class GrailsDomainBinder implements MetadataContributor { if (referenced != null && referenced.isOwningEntity(domainClass) && !isCircularAssociation(grailsProperty)) { cascadeStrategy = CASCADE_ALL; } - else if(isCompositeIdProperty((Mapping) domainClass.getMapping().getMappedForm(), grailsProperty)) { + else if(isCompositeIdProperty(getMapping(domainClass), grailsProperty)) { cascadeStrategy = CASCADE_ALL; } else { @@ -2886,7 +2883,7 @@ public class GrailsDomainBinder implements MetadataContributor { String columnName = null; if (cc == null) { // No column config given, so try to fetch it from the mapping - PersistentEntity domainClass = grailsProp.getOwner(); + PersistentEntity domainClass = grailsProp.getOwner(); Mapping m = getMapping(domainClass); if (m != null) { PropertyConfig c = m.getPropertyConfig(grailsProp.getName()); @@ -2988,8 +2985,7 @@ public class GrailsDomainBinder implements MetadataContributor { private String getIndexColumnType(PersistentProperty property, String defaultType) { PropertyConfig pc = new PersistentPropertyToPropertyConfig().apply(property); if (pc != null && pc.getIndexColumn() != null && pc.getIndexColumn().getType() != null) { - PropertyConfig config = pc.getIndexColumn(); - Mapping mapping = getMapping(property.getOwner()); + Mapping mapping = ((HibernatePersistentEntity)property.getOwner()).getMappedForm(); return new TypeNameProvider().getTypeName(property, mapping); } return defaultType; diff --git a/grails-data-hibernate6/core/src/main/groovy/org/grails/orm/hibernate/cfg/HibernatePersistentEntity.java b/grails-data-hibernate6/core/src/main/groovy/org/grails/orm/hibernate/cfg/HibernatePersistentEntity.java index b23dd23a99..29b07d9527 100644 --- a/grails-data-hibernate6/core/src/main/groovy/org/grails/orm/hibernate/cfg/HibernatePersistentEntity.java +++ b/grails-data-hibernate6/core/src/main/groovy/org/grails/orm/hibernate/cfg/HibernatePersistentEntity.java @@ -17,6 +17,8 @@ package org.grails.orm.hibernate.cfg; import org.grails.datastore.mapping.model.*; +import java.util.Optional; + /** * Persistent entity implementation for Hibernate * @@ -57,4 +59,8 @@ public class HibernatePersistentEntity extends AbstractPersistentEntity<Mapping> public ClassMapping<Mapping> getMapping() { return this.classMapping; } + + public Mapping getMappedForm() { + return Optional.ofNullable(getMapping()).map(ClassMapping::getMappedForm).orElse(null); + } } diff --git a/grails-data-hibernate6/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/ColumnConfigToColumnBinder.java b/grails-data-hibernate6/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/ColumnConfigToColumnBinder.java index 795b31d740..bbeb4aa75a 100644 --- a/grails-data-hibernate6/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/ColumnConfigToColumnBinder.java +++ b/grails-data-hibernate6/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/ColumnConfigToColumnBinder.java @@ -1,12 +1,17 @@ package org.grails.orm.hibernate.cfg.domainbinding; +import jakarta.annotation.Nonnull; import org.grails.orm.hibernate.cfg.ColumnConfig; import org.grails.orm.hibernate.cfg.PropertyConfig; import org.hibernate.mapping.Column; public class ColumnConfigToColumnBinder { - public void bindColumnConfigToColumn(Column column, ColumnConfig columnConfig, PropertyConfig mappedForm) { + public void bindColumnConfigToColumn( + Column column, + ColumnConfig columnConfig, + PropertyConfig mappedForm + ) { if (columnConfig == null) { return; } diff --git a/grails-data-hibernate6/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/EnumTypeBinder.java b/grails-data-hibernate6/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/EnumTypeBinder.java new file mode 100644 index 0000000000..d0357a756d --- /dev/null +++ b/grails-data-hibernate6/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/EnumTypeBinder.java @@ -0,0 +1,98 @@ +package org.grails.orm.hibernate.cfg.domainbinding; + +import org.grails.datastore.mapping.model.PersistentProperty; +import org.grails.orm.hibernate.cfg.ColumnConfig; +import org.grails.orm.hibernate.cfg.HibernatePersistentEntity; +import org.grails.orm.hibernate.cfg.IdentityEnumType; +import org.grails.orm.hibernate.cfg.Mapping; +import org.grails.orm.hibernate.cfg.PropertyConfig; +import org.hibernate.MappingException; +import org.hibernate.mapping.Column; +import org.hibernate.mapping.SimpleValue; +import org.hibernate.mapping.Table; +import org.hibernate.type.EnumType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.sql.Types; +import java.util.Properties; + +import static org.grails.orm.hibernate.cfg.GrailsDomainBinder.DEFAULT_ENUM_TYPE; +import static org.grails.orm.hibernate.cfg.GrailsDomainBinder.ENUM_CLASS_PROP; +import static org.grails.orm.hibernate.cfg.GrailsDomainBinder.ENUM_TYPE_CLASS; + + +public class EnumTypeBinder { + + private final IndexBinder indexBinder; + private ColumnConfigToColumnBinder columnConfigToColumnBinder; + + public EnumTypeBinder() { + this(new IndexBinder(), new ColumnConfigToColumnBinder()); + } + + protected EnumTypeBinder(IndexBinder indexBinder, ColumnConfigToColumnBinder columnConfigToColumnBinder) { + this.indexBinder = indexBinder; + this.columnConfigToColumnBinder = columnConfigToColumnBinder; + } + + + private static final Logger LOG = LoggerFactory.getLogger(EnumTypeBinder.class); + + public void bindEnumType(PersistentProperty property, Class<?> propertyType, SimpleValue simpleValue, String columnName) { + // Create config and mapping objects ONCE and reuse them. + PropertyConfig pc = new PersistentPropertyToPropertyConfig().apply(property); + final HibernatePersistentEntity owner = (HibernatePersistentEntity) property.getOwner(); + Mapping ownerMapping = owner.getMappedForm(); + String enumType = pc.getEnumType(); + Properties enumProperties = new Properties(); + enumProperties.put(ENUM_CLASS_PROP, propertyType.getName()); + String typeName = new TypeNameProvider().getTypeName(property, ownerMapping); + if (typeName != null) { + simpleValue.setTypeName(typeName); + } else { + if (DEFAULT_ENUM_TYPE.equals(enumType) || "string".equalsIgnoreCase(enumType)) { + simpleValue.setTypeName(ENUM_TYPE_CLASS); + enumProperties.put(EnumType.TYPE, String.valueOf(Types.VARCHAR)); + enumProperties.put(EnumType.NAMED, Boolean.TRUE.toString()); + } else if ("ordinal".equalsIgnoreCase(enumType)) { + simpleValue.setTypeName(ENUM_TYPE_CLASS); + enumProperties.put(EnumType.TYPE, String.valueOf(Types.INTEGER)); + enumProperties.put(EnumType.NAMED, Boolean.FALSE.toString()); + } else if ("identity".equals(enumType)) { + simpleValue.setTypeName(IdentityEnumType.class.getName()); + } else { + throw new MappingException("Invalid enum type [" + enumType + "]."); + } + + } + simpleValue.setTypeParameters(enumProperties); + + Column column = new Column(); + boolean isTablePerHierarchySubclass = !owner.isRoot() && (ownerMapping == null || ownerMapping.getTablePerHierarchy()); + if (isTablePerHierarchySubclass) { + // Properties on subclasses in a table-per-hierarchy strategy must be nullable. + if (LOG.isDebugEnabled()) { + LOG.debug("[GrailsDomainBinder] Sub class property [{}] for column name [{}] forced to nullable", + property.getName(), columnName); + } + column.setNullable(true); + } + else { + column.setNullable(property.isNullable()); + } + + column.setValue(simpleValue); + column.setName(columnName); + Table t = simpleValue.getTable(); + t.addColumn(column); + simpleValue.addColumn(column); + + if (!pc.getColumns().isEmpty()) { + ColumnConfig columnConfig = pc.getColumns().get(0); + indexBinder.bindIndex(columnName, column, columnConfig, t); + columnConfigToColumnBinder.bindColumnConfigToColumn(column, columnConfig, pc); + } + } + +} \ No newline at end of file diff --git a/grails-data-hibernate6/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/IndexBinder.java b/grails-data-hibernate6/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/IndexBinder.java index da654bf363..52261107c9 100644 --- a/grails-data-hibernate6/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/IndexBinder.java +++ b/grails-data-hibernate6/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/IndexBinder.java @@ -1,5 +1,6 @@ package org.grails.orm.hibernate.cfg.domainbinding; +import jakarta.annotation.Nonnull; import org.grails.orm.hibernate.cfg.ColumnConfig; import org.hibernate.mapping.Column; import org.hibernate.mapping.Table; @@ -7,7 +8,12 @@ import static java.lang.String.format; import static java.util.Optional.*; public class IndexBinder { - public void bindIndex(String columnName, Column column, ColumnConfig cc, Table table) { + public void bindIndex( + @Nonnull String columnName, + @Nonnull Column column, + ColumnConfig cc, + @Nonnull Table table + ) { ofNullable(cc) .map(ColumnConfig::getIndex) .flatMap(indexObj -> { diff --git a/grails-data-hibernate6/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/PersistentPropertyToPropertyConfig.java b/grails-data-hibernate6/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/PersistentPropertyToPropertyConfig.java index 810f6752ad..53be315bf3 100644 --- a/grails-data-hibernate6/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/PersistentPropertyToPropertyConfig.java +++ b/grails-data-hibernate6/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/PersistentPropertyToPropertyConfig.java @@ -2,6 +2,7 @@ package org.grails.orm.hibernate.cfg.domainbinding; import org.grails.datastore.mapping.model.PersistentProperty; import org.grails.orm.hibernate.cfg.PropertyConfig; +import org.hibernate.MappingException; import java.util.Optional; import java.util.function.Function; @@ -12,6 +13,6 @@ public class PersistentPropertyToPropertyConfig implements Function<PersistentPr return Optional.ofNullable(persistentProperty) .map(PersistentProperty::getMappedForm) .map(PropertyConfig.class::cast) - .orElse(null); + .orElseThrow(() -> new MappingException("No PropertyConfig found for property")); } } diff --git a/grails-data-hibernate6/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/TypeNameProvider.java b/grails-data-hibernate6/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/TypeNameProvider.java index 39ba0d930f..4ff3ce94da 100644 --- a/grails-data-hibernate6/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/TypeNameProvider.java +++ b/grails-data-hibernate6/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/TypeNameProvider.java @@ -18,7 +18,10 @@ public class TypeNameProvider { .map(typeObj -> typeObj instanceof Class<?> clazz ? clazz.getName() : typeObj.toString() ) - .orElse(mapping.getTypeName(property.getType())); + .orElseGet(() -> property != null && mapping != null + ? mapping.getTypeName(property.getType()) : null + ); + } diff --git a/grails-data-hibernate6/core/src/test/groovy/grails/gorm/specs/HibernateGormDatastoreSpec.groovy b/grails-data-hibernate6/core/src/test/groovy/grails/gorm/specs/HibernateGormDatastoreSpec.groovy index cb0697bf1f..0ccfeaa366 100644 --- a/grails-data-hibernate6/core/src/test/groovy/grails/gorm/specs/HibernateGormDatastoreSpec.groovy +++ b/grails-data-hibernate6/core/src/test/groovy/grails/gorm/specs/HibernateGormDatastoreSpec.groovy @@ -63,6 +63,10 @@ class HibernateGormDatastoreSpec extends GrailsDataTckSpec<GrailsDataHibernate6T """ def clazz = classLoader.parseClass(classText) + createPersistentEntity(clazz, binder) + } + + public HibernatePersistentEntity createPersistentEntity(Class clazz, GrailsDomainBinder binder) { def entity = getMappingContext().addPersistentEntity(clazz) as HibernatePersistentEntity binder.evaluateMapping(entity) entity diff --git a/grails-data-hibernate6/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/ColumnConfigToColumnBinderSpec.groovy b/grails-data-hibernate6/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/ColumnConfigToColumnBinderSpec.groovy index 84c12f7390..0df0939a41 100644 --- a/grails-data-hibernate6/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/ColumnConfigToColumnBinderSpec.groovy +++ b/grails-data-hibernate6/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/ColumnConfigToColumnBinderSpec.groovy @@ -10,14 +10,6 @@ class ColumnConfigToColumnBinderSpec extends Specification { def binder = new ColumnConfigToColumnBinder() def column = Mock(Column) - def "should handle null column config"() { - when: - binder.bindColumnConfigToColumn(column, null, null) - - then: - 0 * column._ // verifies no interactions with column - } - def "should bind column properties when values are valid"() { given: def columnConfig = new ColumnConfig() diff --git a/grails-data-hibernate6/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/EnumTypeBinderSpec.groovy b/grails-data-hibernate6/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/EnumTypeBinderSpec.groovy new file mode 100644 index 0000000000..3a5d305dbc --- /dev/null +++ b/grails-data-hibernate6/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/EnumTypeBinderSpec.groovy @@ -0,0 +1,249 @@ +package org.grails.orm.hibernate.cfg.domainbinding + +import grails.gorm.specs.HibernateGormDatastoreSpec +import grails.persistence.Entity +import org.grails.datastore.mapping.model.PersistentProperty +import org.grails.orm.hibernate.cfg.IdentityEnumType +import org.hibernate.engine.spi.SharedSessionContractImplementor +import org.hibernate.mapping.BasicValue +import org.hibernate.mapping.Column +import org.hibernate.mapping.Selectable +import org.hibernate.mapping.Table +import org.hibernate.type.EnumType +import org.hibernate.usertype.UserType +import spock.lang.Subject +import spock.lang.Unroll + +import java.sql.PreparedStatement +import java.sql.ResultSet +import java.sql.SQLException +import java.sql.Types + +class EnumTypeBinderSpec extends HibernateGormDatastoreSpec { + + + + + def indexBinder = Mock(IndexBinder) + def columnBinder = Mock(ColumnConfigToColumnBinder) + + @Subject + EnumTypeBinder binder = new EnumTypeBinder(indexBinder, columnBinder) + + + + @Unroll + def "should bind enum type as #expectedHibernateType when mapping specifies enumType as '#enumTypeMapping'"() { + given: "A root entity and its enum property" + def grailsDomainBinder = getGrailsDomainBinder() + def owner = createPersistentEntity(clazz, grailsDomainBinder) + PersistentProperty property = owner.getPropertyByName("status") + def table = new Table("person") + def simpleValue = new BasicValue(grailsDomainBinder.metadataBuildingContext, table) + + when: "the enum is bound" + binder.bindEnumType(property, Status01, simpleValue, "status_col") + + then: "the correct hibernate type is set" + simpleValue.getTypeName() == expectedHibernateType + simpleValue.isNullable() == nullable + + and: "the type parameters are configured correctly" + def props = simpleValue.getTypeParameters() + (props.getProperty(EnumType.TYPE) == String.valueOf(expectedSqlType)) == typeExpected + (props.getProperty(EnumType.NAMED) == String.valueOf(namedExpected)) == namedIsExpected + + where: + clazz | enumTypeMapping | expectedHibernateType | expectedSqlType | typeExpected | namedExpected | namedIsExpected | nullable + Person01| "default" | EnumType.class.getName() | Types.VARCHAR | true | true | true | false + Person02|"string" | EnumType.class.getName() | Types.VARCHAR | true | true | true | true + Person03|"ordinal" | EnumType.class.getName() | Types.INTEGER | true | false | true | true + Person04|"identity" | IdentityEnumType.class.getName() | null | false | null | false | false + Person05|UserTypeEnumType | UserTypeEnumType.class.getName() | null | false | null | false | false + } + + + + + @Unroll + def "should set column nullability "() { + given: "A root entity and its enum property" + def grailsDomainBinder = getGrailsDomainBinder() + def owner = createPersistentEntity( clazz, grailsDomainBinder) + PersistentProperty property = owner.getPropertyByName("status") + def table = new Table("person") + def simpleValue = new BasicValue(grailsDomainBinder.metadataBuildingContext, table) + def columnName = "status_col" + when: "the enum is bound" + binder.bindEnumType(property, Status01, simpleValue, columnName) + + then: + table.columns.size() == 1 + table.columns[0] == simpleValue.getColumn() + table.columns[0].isNullable() == nullable + table.columns[0].getValue() == simpleValue + table.columns[0].getName() == columnName + + where: + clazz | nullable + Person01| false + Person02| true + Clown01 | true + Clown02 | true + Clown03 | true + + } + + @Unroll + def "should bind index and column constraints only when a column config is present"() { + given: "A root entity and its enum property" + // This test assumes 'indexBinder' and 'columnBinder' are mocked collaborators + // injected via a setup() block, as they are created inside the binder. + def grailsDomainBinder = getGrailsDomainBinder() + def owner = createPersistentEntity(clazz, grailsDomainBinder) + PersistentProperty property = owner.getPropertyByName("status") + def table = new Table("person") + def simpleValue = new BasicValue(grailsDomainBinder.metadataBuildingContext, table) + def columnName = "status_col" + + when: "the enum is bound" + binder.bindEnumType(property, Status01, simpleValue, columnName) + + then: "the index and column binders are invoked the correct number of times" + times * indexBinder.bindIndex(columnName, _ as Column, _, table) + times * columnBinder.bindColumnConfigToColumn(_ as Column, _, _) + + where: + clazz | times + Person01 | 0 + Person02 | 1 + } + + + +} + +class UserTypeEnumType implements UserType { + + @Override + int getSqlType() { + return 0 + } + + @Override + Class returnedClass() { + return null + } + + @Override + boolean equals(Object x, Object y) { + return false + } + + @Override + int hashCode(Object x) { + return 0 + } + + @Override + Object nullSafeGet(ResultSet rs, int position, SharedSessionContractImplementor session, @Deprecated Object owner) throws SQLException { + return null + } + + @Override + void nullSafeSet(PreparedStatement st, Object value, int index, SharedSessionContractImplementor session) throws SQLException { + + } + + @Override + Object deepCopy(Object value) { + return null + } + + @Override + boolean isMutable() { + return false + } + + @Override + Serializable disassemble(Object value) { + return null + } + + @Override + Object assemble(Serializable cached, Object owner) { + return null + } +} + +enum Status01 { + AVAILABLE, OUT_OF_STOCK +} + +enum Status02 { + FOO(3), BAR(5) + Long id + Status02(Long id) { + this.id = id + } +} + +@Entity +class Person01 { + Long id + Status01 status +} + +@Entity +class Person02 { + Long id + Status01 status + static mapping = { + status enumType: "string", nullable :true + + } +} + +@Entity +class Person03 { + Long id + Status01 status + static mapping = { + status enumType: "ordinal", nullable :true + tablePerHierarchy false + + } +} + +@Entity +class Person04 { + Long id + Status02 status + static mapping = { + status enumType: 'identity' + } +} + +@Entity +class Person05 { + Long id + Status02 status + static mapping = { + status type: UserTypeEnumType + } +} + +@Entity +class Clown01 extends Person01 { + String clownName +} + +@Entity +class Clown02 extends Person02 { + String clownName +} + +@Entity +class Clown03 extends Person03 { + String clownName +} \ No newline at end of file diff --git a/grails-data-hibernate6/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/IndexBinderSpec.groovy b/grails-data-hibernate6/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/IndexBinderSpec.groovy index 6ba548370b..fe16ebb41c 100644 --- a/grails-data-hibernate6/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/IndexBinderSpec.groovy +++ b/grails-data-hibernate6/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/IndexBinderSpec.groovy @@ -13,13 +13,6 @@ class IndexBinderSpec extends Specification { def column = Mock(Column) def index = Mock(Index) - def "should not create index when ColumnConfig is null"() { - when: - indexBinder.bindIndex("colName", column, null, table) - - then: - 0 * table._ // verifies no interactions with table - } def "should create default index when index is true"() { given: diff --git a/grails-datastore-core/src/main/groovy/org/grails/datastore/mapping/model/PersistentEntity.java b/grails-datastore-core/src/main/groovy/org/grails/datastore/mapping/model/PersistentEntity.java index 293142ce88..7897702b39 100644 --- a/grails-datastore-core/src/main/groovy/org/grails/datastore/mapping/model/PersistentEntity.java +++ b/grails-datastore-core/src/main/groovy/org/grails/datastore/mapping/model/PersistentEntity.java @@ -18,14 +18,14 @@ */ package org.grails.datastore.mapping.model; -import java.util.List; - import org.grails.datastore.mapping.model.lifecycle.Initializable; import org.grails.datastore.mapping.model.types.Association; import org.grails.datastore.mapping.model.types.Embedded; import org.grails.datastore.mapping.model.types.TenantId; import org.grails.datastore.mapping.reflect.EntityReflector; +import java.util.List; + /** * Represents a persistent entity. * @@ -226,4 +226,6 @@ public interface PersistentEntity extends Initializable { * @return True if the operation was successful */ boolean addOwner(Class type); + + }
