jdaugherty commented on code in PR #16582:
URL: https://github.com/apache/grails-core/pull/16582#discussion_r4289771551


##########
grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/generator/GrailsNativeGenerator.java:
##########
@@ -19,39 +19,59 @@
 package org.grails.orm.hibernate.cfg.domainbinding.generator;
 
 import java.io.Serial;
-import java.lang.reflect.Field;
+import java.util.Properties;
 
 import jakarta.persistence.GenerationType;
 
-import org.hibernate.HibernateException;
+import org.hibernate.boot.model.relational.Database;
+import org.hibernate.boot.model.relational.SqlStringGenerationContext;
+import 
org.hibernate.boot.model.relational.internal.SqlStringGenerationContextImpl;
+import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment;
 import org.hibernate.engine.spi.SharedSessionContractImplementor;
 import org.hibernate.generator.EventType;
 import org.hibernate.generator.GeneratorCreationContext;
 import org.hibernate.id.NativeGenerator;
-import org.hibernate.id.enhanced.SequenceStyleGenerator;
 
 /**
  * A native generator that supports Grails assigned identifiers and fixes 
Hibernate 7 ClassCastException.
  *
+ * <p>Hibernate drives {@link NativeGenerator} through {@code initialize} 
(which picks the delegate for the
+ * dialect), {@code configure} (which configures a sequence or table 
delegate), {@code registerExportables} (which
+ * adds the delegate's sequence or table to the schema export) and {@code 
initialize(SqlStringGenerationContext)}.
+ * Grails creates this generator itself instead of through Hibernate's 
generator binding, so it has to run every one
+ * of those steps. Skipping the middle ones leaves the delegate of a sequence 
based dialect without its sequence.

Review Comment:
   The fix changes what an upgraded application finds in its database, and the 
upgrade guide doesn't cover it yet. On a sequence-based dialect the default id 
mapping now gets one `<table>_SEQ` per entity, where Grails 7 on Hibernate 5 
drew every default-mapped id from the single `hibernate_sequence`. §71 of 
`upgrading80x.adoc` documents that rename only for explicit `id generator: 
'sequence'` mappings; the default mapping is the common case, so a PostgreSQL 
or Oracle application upgrading against an existing schema meets it on every 
domain class. With `dbCreate: validate` startup fails with `Schema validation: 
missing sequence [book_SEQ]`, with `none` the first save fails, and with 
`update` Hibernate creates `book_SEQ` starting at 1, which collides with the 
ids already in the table.
   
   Could you add an upgrade note for the default mapping, next to §71 or folded 
into it? Two things worth stating: an existing schema needs the new sequences 
(the dbmigration snapshot now sees them, which is what the 
`GormIdentifierSnapshot16497Spec` change here shows), and 
`hibernate.id.db_structure_naming_strategy: legacy` keeps `hibernate_sequence` 
for every unnamed sequence, which is the drop-in choice for a database that 
already has it. I checked the latter on PostgreSQL 16 with this branch: the 
default mapping creates `probe_book_seq` as-is and `hibernate_sequence` with 
the `legacy` setting, and the ids come out as 1, 2 either way.
   
   Anchoring here since the doc file isn't in the diff.



##########
grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/generator/GrailsNativeGenerator.java:
##########
@@ -19,39 +19,59 @@
 package org.grails.orm.hibernate.cfg.domainbinding.generator;
 
 import java.io.Serial;
-import java.lang.reflect.Field;
+import java.util.Properties;
 
 import jakarta.persistence.GenerationType;
 
-import org.hibernate.HibernateException;
+import org.hibernate.boot.model.relational.Database;
+import org.hibernate.boot.model.relational.SqlStringGenerationContext;
+import 
org.hibernate.boot.model.relational.internal.SqlStringGenerationContextImpl;
+import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment;
 import org.hibernate.engine.spi.SharedSessionContractImplementor;
 import org.hibernate.generator.EventType;
 import org.hibernate.generator.GeneratorCreationContext;
 import org.hibernate.id.NativeGenerator;
-import org.hibernate.id.enhanced.SequenceStyleGenerator;
 
 /**
  * A native generator that supports Grails assigned identifiers and fixes 
Hibernate 7 ClassCastException.
  *
+ * <p>Hibernate drives {@link NativeGenerator} through {@code initialize} 
(which picks the delegate for the
+ * dialect), {@code configure} (which configures a sequence or table 
delegate), {@code registerExportables} (which
+ * adds the delegate's sequence or table to the schema export) and {@code 
initialize(SqlStringGenerationContext)}.
+ * Grails creates this generator itself instead of through Hibernate's 
generator binding, so it has to run every one
+ * of those steps. Skipping the middle ones leaves the delegate of a sequence 
based dialect without its sequence.
+ *
  * @since 8.0
  */
 public class GrailsNativeGenerator extends NativeGenerator {
 
     @Serial
     private static final long serialVersionUID = 1L;
 
-    public GrailsNativeGenerator(GeneratorCreationContext context) {
-        // This triggers the internal switch logic in NativeGenerator,
-        // which calls setIdentity(true) on the column for H2.
-        try {
-            this.initialize(null, null, context);
-        } catch (Exception ignored) {
-            // ignore for now, helps with testing robustness where context 
might be incomplete
-        }
+    /** Carries the default {@code @NativeGenerator} the way Hibernate reads 
it from an unannotated identifier. */
+    @org.hibernate.annotations.NativeGenerator
+    private static final class DefaultNativeGenerator {}
+
+    private static final org.hibernate.annotations.NativeGenerator 
DEFAULT_ANNOTATION =
+            
DefaultNativeGenerator.class.getAnnotation(org.hibernate.annotations.NativeGenerator.class);
+
+    public GrailsNativeGenerator(GeneratorCreationContext context, 
JdbcEnvironment jdbcEnvironment) {
+        // Picks the delegate for the dialect; for IDENTITY it also calls 
setIdentity(true) on the column.
+        this.initialize(DEFAULT_ANNOTATION, null, context);
+        this.configure(context, new Properties());
+
+        Database database = context.getDatabase();
+        this.registerExportables(database);
+
+        var physicalName = database.getDefaultNamespace().getPhysicalName();
+        String catalog = (physicalName.catalog() != null) ? 
physicalName.catalog().getCanonicalName() : null;
+        String schema = (physicalName.schema() != null) ? 
physicalName.schema().getCanonicalName() : null;
+        SqlStringGenerationContext sqlContext =
+                SqlStringGenerationContextImpl.fromExplicit(jdbcEnvironment, 
database, catalog, schema);

Review Comment:
   Optional, and the two sibling generators do the same, so it needn't change 
in this PR. `database.getDefaultNamespace()` is `locateNamespace(null, null)`, 
so its physical name carries no catalog or schema under the standard physical 
naming: the two branches above are dead and this is always 
`fromExplicit(jdbcEnvironment, database, null, null)`, which means 
`hibernate.default_schema` and `hibernate.default_catalog` never reach the 
generator's rendered `next value` SQL even though the schema export qualifies 
the `create sequence` with them. Hibernate's own binder passes 
`creationContext.getSqlStringGenerationContext()` 
(`GeneratorBinder.callConfigure`), which carries those defaults, and 
`GeneratorCreationContextWrapper` already forwards it, so 
`this.initialize(context.getSqlStringGenerationContext())` would do the same 
job without the `JdbcEnvironment` parameter. Happy for that to be a follow-up 
covering all three generators together.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to