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 d84b7e0d96c3d50ab0c92960860f06b71f8d51e8
Author: Walter B Duque de Estrada <[email protected]>
AuthorDate: Thu Jan 15 13:09:22 2026 -0600

    Fix(hibernate7): Resolve DDL generation issues and update 
NamingStrategyWrapperSpec
    
    Updated NamingStrategyWrapper to replace dots with underscores in logical 
names
    before passing them to Hibernate\'s PhysicalNamingStrategy. This fixes
    JdbcSQLSyntaxErrorException during DDL generation.
    
    Also updated HIBERNATE7-UPGRADE-PROGRESS.md and added corresponding tests
    to NamingStrategyWrapperSpec.
---
 .../core/HIBERNATE7-UPGRADE-PROGRESS.md            | 16 +++++---
 .../cfg/domainbinding/NamingStrategyWrapper.java   |  6 ++-
 .../domainbinding/NamingStrategyWrapperSpec.groovy | 43 +++++++++++++++++++++-
 3 files changed, 57 insertions(+), 8 deletions(-)

diff --git a/grails-data-hibernate7/core/HIBERNATE7-UPGRADE-PROGRESS.md 
b/grails-data-hibernate7/core/HIBERNATE7-UPGRADE-PROGRESS.md
index bec594ac79..10982450f1 100644
--- a/grails-data-hibernate7/core/HIBERNATE7-UPGRADE-PROGRESS.md
+++ b/grails-data-hibernate7/core/HIBERNATE7-UPGRADE-PROGRESS.md
@@ -44,21 +44,27 @@ This document summarizes the approaches taken, challenges 
encountered, and futur
     - No other direct `session.save()` calls found in `src/main` or `src/test` 
of the `core` module.
     - Systematic audit of other modules and TCK is still required.
 
+### 7. Fixed DDL Generation Issues
+- **Approach:** Updated `NamingStrategyWrapper` to globally replace dots with 
underscores in logical class names before passing them to Hibernate's 
`PhysicalNamingStrategy`.
+- **Reasoning:** Hibernate 7's default naming strategies preserve dots in 
logical names (e.g., from FQCNs), which leads to invalid SQL in databases like 
H2. GORM expects underscores for compatibility and valid SQL.
+- **Result:** Resolved `JdbcSQLSyntaxErrorException` in tests like 
`CascadeBehaviorPersisterSpec`, where join table columns now use valid 
underscore-delimited names instead of dotted class names.
+
 ## Challenges & Failures
 
 ### 1. Proxy Initialization Behavior
 - **Issue:** In `Hibernate6GroovyProxySpec`, `Location.proxy(id)` returns an 
object that is already initialized (`Hibernate.isInitialized(location) == 
true`), even after clearing the session.
 - **Attempts:** Tried `session.getReference()`, 
`session.byId().getReference()`, and using fresh sessions.
-- **Status:** Ongoing investigation. Debugging indicates that even native 
Hibernate proxies might be reporting as initialized or are being initialized 
during the proxy creation/retrieval process in the test environment.
+- **Status:** Ongoing investigation. Debugging indicates that Hibernate 7's 
bytecode enhancement or session management might be reporting Groovy proxies as 
initialized even when they haven't fetched their target.
 
-### 2. SQL Syntax Errors in DDL
-- **Issue:** Several tests (e.g., `CascadeBehaviorPersisterSpec`) show 
`JdbcSQLSyntaxErrorException` during schema creation.
-- **Observation:** DDL statements are attempting to use qualified class names 
as column names (e.g., `create table ... (org.grails.orm..._id bigint)`), which 
fails in H2. This is likely related to how Hibernate 7 handles component or 
join column naming when dots are present.
+### 2. SQL Syntax Errors in DDL (RESOLVED)
+- **Issue:** Several tests showed `JdbcSQLSyntaxErrorException` during schema 
creation due to dots in column names.
+- **Solution:** Centralized dot-to-underscore replacement in 
`NamingStrategyWrapper`.
 
-### 3. Missing Methods in Proxies
+### 3. Missing Methods in Proxies (RESOLVED)
 - **Issue:** Hibernate 7 proxies no longer implement `isInitialized()` or 
`getInitialized()` directly on the proxy object.
 - **Solution:** Switched to `Hibernate.isInitialized(proxy)`.
 
+
 ## Strategy for GrailsDomainBinder Refactoring
 
 ### Goal
diff --git 
a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/NamingStrategyWrapper.java
 
b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/NamingStrategyWrapper.java
index 6099c49e95..3f829c8f99 100644
--- 
a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/NamingStrategyWrapper.java
+++ 
b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/NamingStrategyWrapper.java
@@ -37,25 +37,27 @@ public class NamingStrategyWrapper implements 
PersistentEntityNamingStrategy {
     @Override
     public String resolveColumnName(String logicalName) {
         return Optional.ofNullable(logicalName)
+                .map(name -> name.replace('.', '_'))
                 .flatMap(name ->
                         // Safely handle a null return from the strategy by 
wrapping it in an Optional.
                         
Optional.ofNullable(namingStrategy.toPhysicalColumnName(toIdentifier(name), 
jdbcEnvironment))
                 )
                 .map(Identifier::getText)
                 // Per Hibernate contract, if the strategy returns null, use 
the original logical name.
-                .orElse(logicalName);
+                .orElseGet(() -> logicalName != null ? 
logicalName.replace('.', '_') : null);
     }
 
     @Override
     public String resolveTableName(String logicalName) {
         return Optional.ofNullable(logicalName)
+                .map(name -> name.replace('.', '_'))
                 .flatMap(name ->
                         // Safely handle a null return from the strategy.
                         
Optional.ofNullable(namingStrategy.toPhysicalTableName(toIdentifier(name), 
jdbcEnvironment))
                 )
                 .map(Identifier::getText)
                 // Per Hibernate contract, if the strategy returns null, use 
the original logical name.
-                .orElse(logicalName);
+                .orElseGet(() -> logicalName != null ? 
logicalName.replace('.', '_') : null);
     }
 
     @Override
diff --git 
a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/NamingStrategyWrapperSpec.groovy
 
b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/NamingStrategyWrapperSpec.groovy
index 29a7fba47a..59b76a4cfd 100644
--- 
a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/NamingStrategyWrapperSpec.groovy
+++ 
b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/NamingStrategyWrapperSpec.groovy
@@ -132,6 +132,46 @@ class NamingStrategyWrapperSpec extends 
HibernateGormDatastoreSpec {
         and: "The wrapped strategy was called with an identifier based on the 
decapitalized owner name"
         capturedIdentifier.text == decapitalizedOwnerName
     }
+
+    def "should replace dots with underscores for logical column name before 
passing to wrapped strategy"() {
+        given:
+        def logicalNameWithDots = "com.example.MyClass.myProperty"
+        def expectedLogicalNameForStrategy = "com_example_MyClass_myProperty"
+        def expectedPhysicalName = "com_example_my_class_my_property"
+        def capturedIdentifier
+
+        mockStrategy.toPhysicalColumnName(_, mockJdbcEnv) >> { Identifier id, 
JdbcEnvironment env ->
+            capturedIdentifier = id
+            return Identifier.toIdentifier(expectedPhysicalName)
+        }
+
+        when:
+        def actualResult = wrapper.resolveColumnName(logicalNameWithDots)
+
+        then:
+        actualResult == expectedPhysicalName
+        capturedIdentifier.text == expectedLogicalNameForStrategy
+    }
+
+    def "should replace dots with underscores for logical table name before 
passing to wrapped strategy"() {
+        given:
+        def logicalNameWithDots = "com.example.MyClass"
+        def expectedLogicalNameForStrategy = "com_example_MyClass"
+        def expectedPhysicalName = "com_example_my_class"
+        def capturedIdentifier
+
+        mockStrategy.toPhysicalTableName(_, mockJdbcEnv) >> { Identifier id, 
JdbcEnvironment env ->
+            capturedIdentifier = id
+            return Identifier.toIdentifier(expectedPhysicalName)
+        }
+
+        when:
+        def actualResult = wrapper.resolveTableName(logicalNameWithDots)
+
+        then:
+        actualResult == expectedPhysicalName
+        capturedIdentifier.text == expectedLogicalNameForStrategy
+    }
 }
 
 // Helper domain class for testing
@@ -139,4 +179,5 @@ class NamingStrategyWrapperSpec extends 
HibernateGormDatastoreSpec {
 class Owner {
     Long id
     String someProperty
-}
\ No newline at end of file
+}
+

Reply via email to