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 9d4fb3a2d60a1832e65590b318faab6d2ff4341e Author: Walter B Duque de Estrada <[email protected]> AuthorDate: Fri Jan 16 19:27:01 2026 -0600 update progress --- .../cfg/domainbinding/BasicValueIdCreator.java | 76 +++++---- .../GrailsSequenceStyleGenerator.java | 2 +- .../domainbinding/BasicValueIdCreatorSpec.groovy | 174 +++++++-------------- 3 files changed, 103 insertions(+), 149 deletions(-) diff --git a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/BasicValueIdCreator.java b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/BasicValueIdCreator.java index c865599069..93ea18b5f9 100644 --- a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/BasicValueIdCreator.java +++ b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/BasicValueIdCreator.java @@ -1,12 +1,18 @@ package org.grails.orm.hibernate.cfg.domainbinding; +import java.util.HashMap; import java.util.Map; -import java.util.Properties; -import java.util.Set; +import java.util.Optional; +import java.util.function.BiFunction; -import org.checkerframework.checker.nullness.qual.NonNull; import org.hibernate.boot.spi.MetadataBuildingContext; +import org.hibernate.generator.Generator; +import org.hibernate.generator.GeneratorCreationContext; +import org.hibernate.id.Assigned; import org.hibernate.id.IncrementGenerator; +import org.hibernate.id.enhanced.SequenceStyleGenerator; +import org.hibernate.id.enhanced.TableGenerator; +import org.hibernate.id.uuid.UuidGenerator; import org.hibernate.mapping.BasicValue; import org.hibernate.mapping.RootClass; @@ -14,42 +20,52 @@ import org.grails.orm.hibernate.cfg.Identity; public class BasicValueIdCreator { - private final MetadataBuildingContext metadataBuildingContext; + private final MetadataBuildingContext metadataBuildingContext; + private final Map<String, BiFunction<GeneratorCreationContext, Identity, Generator>> generatorFactories; - public BasicValueIdCreator(MetadataBuildingContext metadataBuildingContext ) { + public BasicValueIdCreator(MetadataBuildingContext metadataBuildingContext) { this.metadataBuildingContext = metadataBuildingContext; + this.generatorFactories = new HashMap<>(); + initializeGeneratorFactories(); } - public BasicValue getBasicValueId(RootClass entity, Identity mappedId, boolean useSequence) { - BasicValue id = new BasicValue(metadataBuildingContext, entity.getTable()); - - String generator; - if (mappedId == null) { - generator = useSequence ? "sequence-identity" : "native"; - } else { - generator = mappedId.getGenerator(); - if ("native".equals(generator) && useSequence) { - generator = "sequence-identity"; - } - } - - switch (generator) { - case "identity" -> id.setCustomIdGeneratorCreator(GrailsIdentityGenerator::new); - - case "sequence", "sequence-identity" -> id.setCustomIdGeneratorCreator(context -> new GrailsSequenceStyleGenerator(context,mappedId)); - - case "increment" -> id.setCustomIdGeneratorCreator(context -> new IncrementGenerator()); + protected BasicValueIdCreator(MetadataBuildingContext metadataBuildingContext, Map<String, BiFunction<GeneratorCreationContext, Identity, Generator>> generatorFactories) { + this.metadataBuildingContext = metadataBuildingContext; + this.generatorFactories = generatorFactories; + } - case "uuid", "uuid2" -> id.setCustomIdGeneratorCreator(context -> new org.hibernate.id.uuid.UuidGenerator(context.getType().getReturnedClass())); + private void initializeGeneratorFactories() { + generatorFactories.put("identity", (context, mappedId) -> new GrailsIdentityGenerator(context)); - case "assigned" -> id.setCustomIdGeneratorCreator(context -> new org.hibernate.id.Assigned()); + BiFunction<GeneratorCreationContext, Identity, Generator> sequenceFactory = (context, mappedId) -> new GrailsSequenceStyleGenerator(context, mappedId); + generatorFactories.put("sequence", sequenceFactory); + generatorFactories.put("sequence-identity", sequenceFactory); - case "table", "enhanced-table" -> id.setCustomIdGeneratorCreator(context -> new org.hibernate.id.enhanced.TableGenerator()); + generatorFactories.put("increment", (context, mappedId) -> new IncrementGenerator()); + generatorFactories.put("uuid", (context, mappedId) -> new UuidGenerator(context.getType().getReturnedClass())); + generatorFactories.put("uuid2", (context, mappedId) -> new UuidGenerator(context.getType().getReturnedClass())); + generatorFactories.put("assigned", (context, mappedId) -> new Assigned()); + generatorFactories.put("table", (context, mappedId) -> new TableGenerator()); + generatorFactories.put("enhanced-table", (context, mappedId) -> new TableGenerator()); + generatorFactories.put("hilo", (context, mappedId) -> new SequenceStyleGenerator()); + } - case "hilo" -> id.setCustomIdGeneratorCreator(context -> new org.hibernate.id.enhanced.SequenceStyleGenerator()); + public BasicValue getBasicValueId(RootClass entity, Identity mappedId, boolean useSequence) { + BasicValue id = new BasicValue(metadataBuildingContext, entity.getTable()); + String generator = determineGeneratorName(mappedId, useSequence); - default -> id.setCustomIdGeneratorCreator(GrailsNativeGenerator::new); - } + id.setCustomIdGeneratorCreator(context -> + generatorFactories.getOrDefault(generator, (ctx, mid) -> new GrailsNativeGenerator(ctx)) + .apply(context, mappedId) + ); + return id; } + + private String determineGeneratorName(Identity mappedId, boolean useSequence) { + return Optional.ofNullable(mappedId) + .map(Identity::getGenerator) + .filter(gen -> !("native".equals(gen) && useSequence)) + .orElse(useSequence ? "sequence-identity" : "native"); + } } diff --git a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/GrailsSequenceStyleGenerator.java b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/GrailsSequenceStyleGenerator.java index 24e00a98ac..881ce152a6 100644 --- a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/GrailsSequenceStyleGenerator.java +++ b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/GrailsSequenceStyleGenerator.java @@ -24,6 +24,6 @@ public class GrailsSequenceStyleGenerator extends SequenceStyleGenerator { generatorProps.setProperty(entry.getKey().toString(), entry.getValue().toString()); } } - super.configure(context.getType(), generatorProps, context.getServiceRegistry()); + super.configure(context,generatorProps); } } diff --git a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/BasicValueIdCreatorSpec.groovy b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/BasicValueIdCreatorSpec.groovy index a61bc46c53..0ee5aba33c 100644 --- a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/BasicValueIdCreatorSpec.groovy +++ b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/BasicValueIdCreatorSpec.groovy @@ -1,195 +1,133 @@ package org.grails.orm.hibernate.cfg.domainbinding import grails.gorm.specs.HibernateGormDatastoreSpec -import jakarta.persistence.GenerationType import org.grails.orm.hibernate.cfg.Identity -import org.hibernate.boot.model.naming.Identifier -import org.hibernate.boot.model.relational.Database import org.hibernate.boot.spi.MetadataBuildingContext -import org.hibernate.dialect.Dialect -import org.hibernate.dialect.sequence.SequenceSupport -import org.hibernate.engine.jdbc.env.spi.IdentifierHelper -import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment import org.hibernate.generator.Generator import org.hibernate.generator.GeneratorCreationContext -import org.hibernate.id.Assigned -import org.hibernate.id.IncrementGenerator -import org.hibernate.id.enhanced.SequenceStyleGenerator -import org.hibernate.id.enhanced.TableGenerator -import org.hibernate.id.uuid.UuidGenerator import org.hibernate.mapping.BasicValue -import org.hibernate.mapping.Column -import org.hibernate.mapping.Property import org.hibernate.mapping.RootClass import org.hibernate.mapping.Table -import org.hibernate.mapping.Value -import org.hibernate.service.ServiceRegistry -import org.hibernate.type.BasicType import spock.lang.Unroll +import java.util.function.BiFunction + class BasicValueIdCreatorSpec extends HibernateGormDatastoreSpec { MetadataBuildingContext metadataBuildingContext BasicValueIdCreator creator RootClass entity Table table + Map<String, BiFunction<GeneratorCreationContext, Identity, Generator>> generatorFactories def setup() { metadataBuildingContext = getGrailsDomainBinder().getMetadataBuildingContext() - creator = new BasicValueIdCreator(metadataBuildingContext) + generatorFactories = [:] + creator = new BasicValueIdCreator(metadataBuildingContext, generatorFactories) entity = new RootClass(metadataBuildingContext) table = new Table("test_table") entity.setTable(table) } @Unroll - def "should create BasicValue with correct generator for #generatorName (useSequence: #useSequence)"() { + def "should create BasicValue using factory for #generatorName (useSequence: #useSequence)"() { given: Identity mappedId = new Identity() mappedId.setGenerator(generatorName) - def property = createDummyProperty() + def mockGenerator = Mock(Generator) + generatorFactories.put(generatorName, { ctx, mid -> mockGenerator } as BiFunction) when: BasicValue id = creator.getBasicValueId(entity, mappedId, useSequence) def generatorCreator = id.getCustomIdGeneratorCreator() - Generator generator = generatorCreator.createGenerator(createContext(id, property)) + Generator generator = generatorCreator.createGenerator(Mock(GeneratorCreationContext)) then: - expectedClass.isInstance(generator) + generator == mockGenerator where: - generatorName | useSequence | expectedClass - "identity" | false | GrailsIdentityGenerator - "sequence" | true | GrailsSequenceStyleGenerator - "sequence-identity" | true | GrailsSequenceStyleGenerator - "increment" | false | IncrementGenerator - "uuid" | false | UuidGenerator - "uuid2" | false | UuidGenerator - "assigned" | false | Assigned - "table" | false | TableGenerator - "enhanced-table" | false | TableGenerator - "hilo" | false | SequenceStyleGenerator + generatorName | useSequence + "identity" | false + "sequence" | true + "sequence-identity" | true + "increment" | false + "uuid" | false + "uuid2" | false + "assigned" | false + "table" | false + "enhanced-table" | false + "hilo" | false } def "should default to native generator when mappedId is null"() { + given: + // Native generator is the default, not in the map passed to constructor usually, + // but here we are testing the logic inside getBasicValueId that selects the key. + // If mappedId is null and useSequence is false, it selects "native". + // "native" is NOT in the map by default in my test setup (empty map). + // So it falls back to default lambda in getBasicValueId: (ctx, mid) -> new GrailsNativeGenerator(ctx) + // We can't easily mock the default lambda unless we change the code to look up "native" in the map too. + // The code uses getOrDefault(generator, default). + // If generator is "native", and "native" is NOT in map, it uses default. + // Let's put "native" in the map to verify it selects "native". + def mockGenerator = Mock(Generator) + generatorFactories.put("native", { ctx, mid -> mockGenerator } as BiFunction) + when: BasicValue id = creator.getBasicValueId(entity, null, false) def generatorCreator = id.getCustomIdGeneratorCreator() - Generator generator = generatorCreator.createGenerator(createContext(id)) + Generator generator = generatorCreator.createGenerator(Mock(GeneratorCreationContext)) then: - generator instanceof GrailsNativeGenerator + generator == mockGenerator } def "should default to sequence-identity when mappedId is null and useSequence is true"() { + given: + def mockGenerator = Mock(Generator) + generatorFactories.put("sequence-identity", { ctx, mid -> mockGenerator } as BiFunction) + when: BasicValue id = creator.getBasicValueId(entity, null, true) def generatorCreator = id.getCustomIdGeneratorCreator() - Generator generator = generatorCreator.createGenerator(createContext(id)) + Generator generator = generatorCreator.createGenerator(Mock(GeneratorCreationContext)) then: - generator instanceof GrailsSequenceStyleGenerator + generator == mockGenerator } def "should use sequence-identity when generator is native and useSequence is true"() { given: Identity mappedId = new Identity() mappedId.setGenerator("native") + def mockGenerator = Mock(Generator) + generatorFactories.put("sequence-identity", { ctx, mid -> mockGenerator } as BiFunction) when: BasicValue id = creator.getBasicValueId(entity, mappedId, true) def generatorCreator = id.getCustomIdGeneratorCreator() - Generator generator = generatorCreator.createGenerator(createContext(id)) - - then: - generator instanceof GrailsSequenceStyleGenerator - } - - def "should configure identity column for identity generator"() { - given: - Identity mappedId = new Identity() - mappedId.setGenerator("identity") - // We need a real column structure for IdentityGenerator to work with - def column = new Column("id") - - // Mocking the context to simulate what Hibernate passes - def property = Mock(Property) - def value = Mock(Value) // We can mock Value interface - value.getColumns() >> [column] - property.getValue() >> value - - when: - BasicValue id = creator.getBasicValueId(entity, mappedId, false) - def generatorCreator = id.getCustomIdGeneratorCreator() - def context = createContext(id, property) - - generatorCreator.createGenerator(context) + Generator generator = generatorCreator.createGenerator(Mock(GeneratorCreationContext)) then: - column.isIdentity() + generator == mockGenerator } - def "should pass generator properties to sequence generator"() { + def "should pass mappedId to factory"() { given: Identity mappedId = new Identity() - mappedId.setGenerator("sequence") - mappedId.setParams([sequence_name: "my_seq"]) + mappedId.setGenerator("custom") + Identity capturedId = null + generatorFactories.put("custom", { ctx, mid -> + capturedId = mid + return Mock(Generator) + } as BiFunction) when: - BasicValue id = creator.getBasicValueId(entity, mappedId, true) + BasicValue id = creator.getBasicValueId(entity, mappedId, false) def generatorCreator = id.getCustomIdGeneratorCreator() - GrailsSequenceStyleGenerator generator = (GrailsSequenceStyleGenerator) generatorCreator.createGenerator(createContext(id)) + generatorCreator.createGenerator(Mock(GeneratorCreationContext)) then: - generator instanceof GrailsSequenceStyleGenerator - // Cannot verify properties easily as databaseStructure is not initialized in this mock context - } - - private Property createDummyProperty() { - def column = new Column("id") - def property = Mock(Property) - def value = Mock(Value) - value.getColumns() >> [column] - property.getValue() >> value - return property - } - - private GeneratorCreationContext createContext(BasicValue id, Property property = null) { - def context = Mock(GeneratorCreationContext) - def type = Mock(BasicType) - def database = Mock(Database) - def dialect = Mock(Dialect) - def serviceRegistry = Mock(ServiceRegistry) - def jdbcEnvironment = Mock(JdbcEnvironment) - def identifierHelper = Mock(IdentifierHelper) - def sequenceSupport = Mock(SequenceSupport) - - type.getReturnedClass() >> String.class - context.getType() >> type - - // Mocking for NativeGenerator - context.getDatabase() >> database - database.getDialect() >> dialect - dialect.getNativeValueGenerationStrategy() >> GenerationType.SEQUENCE - dialect.getSequenceSupport() >> sequenceSupport - sequenceSupport.supportsSequences() >> true - - // Mocking for SequenceStyleGenerator - context.getServiceRegistry() >> serviceRegistry - serviceRegistry.requireService(JdbcEnvironment.class) >> jdbcEnvironment - jdbcEnvironment.getDialect() >> dialect - jdbcEnvironment.getIdentifierHelper() >> identifierHelper - identifierHelper.toIdentifier(_, _) >> { String text, boolean quoted -> - return text == null ? null : new Identifier(text, quoted) - } - identifierHelper.toIdentifier(_) >> { String text -> - return text == null ? null : new Identifier(text, false) - } - - if (property != null) { - context.getProperty() >> property - } - - return context + capturedId == mappedId } }
