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 +} +
