Merged
Conversation
GrailsOpenSessionInViewInterceptor only registered a session for the default datasource SessionFactory. Calling withSession on a secondary datasource during a web request threw 'No Session found for current thread' because no session was bound for that SessionFactory. The interceptor now iterates all connection sources from the HibernateDatastore and opens/binds sessions for each non-default datasource in preHandle, flushes them in postHandle, and unbinds/closes them in afterCompletion. Existing sessions that are already bound (e.g. by a transaction) are left untouched. Fixes #14333 Fixes #11798 Assisted-by: Claude Code <Claude@Claude.ai>
Wrap the additional session cleanup loop in try-finally so that super.afterCompletion() always runs even if closing an additional session throws. Add per-session try-catch around closeSession with error logging, matching HibernatePersistenceContextInterceptor.destroy() pattern. Assisted-by: Claude Code <Claude@Claude.ai>
Include the datasource connection name in debug and error log messages for GrailsOpenSessionInViewInterceptor to aid multi-datasource debugging. Add a Geb integration test in the datasources test module that verifies OSIV keeps the secondary datasource session open during GSP view rendering, allowing lazy-loaded associations to be accessed without LazyInitializationException. Assisted-by: Claude Code <Claude@Claude.ai>
Use Book.secondary.findByTitle instead of Book.secondary.first to avoid picking up stale records from other integration tests sharing the same H2 database. Assisted-by: Claude Code <Claude@Claude.ai>
Add two new TCK specs that validate GORM Data Service CRUD operations route correctly to secondary datasources across all datastore implementations: - DataServiceConnectionRoutingSpec: 16 tests covering save, get, count, delete, findByName, findAllByName, and constructor-style save through both abstract class and interface service patterns, plus cross-datasource isolation verification. - DataServiceMultiTenantConnectionRoutingSpec: 5 tests covering DISCRIMINATOR multi-tenancy combined with secondary datasource routing, verifying tenant isolation for save, get, count, delete, and findByName. Adds multi-tenant + multi-datasource infrastructure to TCK: - GrailsDataTckManager: new supportsMultiTenantMultiDataSource(), setupMultiTenantMultiDataSource(), cleanupMultiTenantMultiDataSource(), getServiceForMultiTenantConnection() methods - GrailsDataHibernate5TckManager: DISCRIMINATOR mode with SystemPropertyTenantResolver and dual H2 in-memory databases - GrailsDataMongoTckManager: DISCRIMINATOR mode with SystemPropertyTenantResolver and dual MongoDB databases Fixes AbstractDatastoreInitializer.loadDataServices() to filter discovered services by mapped domain classes, preventing classpath pollution from unrelated @service implementations causing startup failures in standalone initializers (e.g., MongoDbDataStoreSpringInitializerSpec). Assisted-by: Claude Code <Claude@Claude.ai>
Add four new TCK specs that test GORM operations at the domain level via GormEnhancer API, and verify cross-layer consistency between domain operations and Data Service operations: - DomainMultiDataSourceSpec: 8 tests for direct save, get, count, list, criteria query, delete on secondary datasource, plus cross-datasource isolation. - DomainMultiTenantMultiDataSourceSpec: 5 tests for DISCRIMINATOR multi-tenancy with direct domain operations on secondary datasource, including tenant-scoped count, criteria query, and isolation. - CrossLayerMultiDataSourceSpec: 5 tests verifying data written at the domain level is visible through Data Services and vice versa, including delete propagation and count consistency. - CrossLayerMultiTenantMultiDataSourceSpec: 3 tests verifying tenant-isolated data is consistent across domain API and Data Service layers. Assisted-by: Claude Code <Claude@Claude.ai>
… and functional test suites Verify that GORM static methods (executeQuery, withCriteria, createCriteria, executeUpdate, withTransaction) route to the correct datasource for entities mapped to non-default datasources. Covers the allQualifiers() fix in 4e04e96. Assisted-by: Claude Code <Claude@Claude.ai>
…iTenant routing, and CRUD connection fixes Add documentation reflecting the post-fix state after PRs #15393, #15395, and #15396 are merged: - Add @CompileStatic + injected @service property example (PR #15396) - Add Multi-Tenancy with explicit datasource section (PR #15393) - List all CRUD methods that respect connection routing (PR #15395) - Soften IMPORTANT boxes to NOTE with authoritative tone Assisted-by: Claude Code <Claude@Claude.ai>
…list Add @Where-annotated methods, @Query-annotated methods, and DetachedCriteria-based queries to the list of auto-implemented methods that respect the connection parameter in multi-datasource Data Services documentation. Assisted-by: Claude Code <Claude@Claude.ai>
Domain static methods like Book.executeQuery(), Book.createCriteria(), and Book.withCriteria() route to the correct datasource automatically when the domain declares a non-default datasource in its mapping block. Remove all references to GormEnhancer.findStaticApi() from user-facing documentation since it is an internal API that users no longer need. Assisted-by: Claude Code <Claude@Claude.ai>
…cription Remove GormEnhancer class name from multi-tenancy explanation since it is an internal implementation detail. Fix description of how @service properties are populated to say autowiring by type instead of datastore.getService(). Assisted-by: Claude Code <Claude@Claude.ai>
… APIs
Tests were calling GormEnhancer.findStaticApi(), findInstanceApi(), and
findValidationApi() to interact with GORM internals directly. Replace all
usages with the same public APIs an end user would call.
Changes per file:
- WhereQueryConnectionRoutingSpec: use Domain."${connectionName}"
named-datasource property + instance .save()/.delete() directly
- FooIntegrationSpec / BarIntegrationSpec: use Domain.withNewSession
to access the backing datastore
- SaveWithFailOnErrorDefaultSpec: remove mutation of internal failOnError
state; test per-call save(failOnError: true/false) behaviour instead
- DeepValidateWithSaveSpec: drop GormInstanceApi mock; use real entity
with entity.save(deepValidate: true/false)
- DataServiceMultiTenantMultiDataSourceSpec: replace API-registration
assertion with functional save/count via data service; replace
GormEnhancer in MetricService with Metric.executeQuery/executeUpdate
(safe because the service is @transactional(connection='analytics'))
- DataServiceMultiDataSourceSpec: add @query deleteAll()/getTotalAmount()
to ProductService; replace all GormEnhancer calls with data service
and domain static methods
- ValidationSpec (neo4j): use mappingContext.addEntityValidator(entity, null)
to clear validators instead of GormEnhancer.findValidationApi()
All affected tests pass.
Assisted-by: Claude Code <Claude@Claude.ai>
…QueryConnectionRoutingSpec Call ."connectionName".save() and ."connectionName".delete() on entity instances so that GORM routes the operation through the correct datasource session, avoiding 'No Session found for current thread' when saving inside a named-connection withNewTransaction block. Assisted-by: Claude Code <Claude@Claude.ai>
…strengthen DeepValidateWithSaveSpec - Add missing trailing newline to satisfy CodeNarc FileEndsWithoutNewline rule - Rewrite DeepValidateWithSaveSpec to install a CascadingValidator mock and verify the deepValidate argument is actually passed through to validate(), rather than just checking that a valid entity can be saved either way Assisted-by: Claude Code <Claude@Claude.ai>
Database cleanup feature feedback
The 'ersatz listener captures request details' test in MicronautErsatzAdvancedSpec intermittently fails because the Ersatz listener callback executes asynchronously on a server thread while the test assertion runs immediately on the test thread. Use a thread-safe CopyOnWriteArrayList and Spock PollingConditions to wait for the listener to populate before asserting. Assisted-by: OpenCode <opencode@opencode.ai>
fix: flaky ersatz listener test due to async race condition
feature - add `@DatabaseCleanup` test annoation to facilicate cleaning up after a test or tests
fix: extend OSIV to manage sessions for all datasources
DetachedCriteria joins with explicit join types were not updating HibernateQuery join state used by single-result execution. This caused get()/singleResult() paths to diverge from list() behavior for join fetch application.\n\nAdd an override of join(String, JoinType) in AbstractHibernateQuery that sets hasJoins, records joinTypes, and applies FetchMode.JOIN to criteria/detachedCriteria.\n\nAlso add a regression test in DetachedCriteriaJoinSpec proving Team.where { ... }.join('club').get() eagerly initializes the club association.\n\nCo-Authored-By: Oz <oz-agent@warp.dev>
DetachedCriteria join('club') with property('club.name') failed unless an explicit createAlias('club','club') was provided. The query path for detached projections adapted projections directly and did not normalize nested association property paths into Hibernate aliases.\n\nAdd projection-property normalization in AbstractHibernateQuery to auto-create aliases for association paths and map property-based projections to aliased paths before adapting to Hibernate projections.\n\nAdd regression tests for both list() and get() proving join('club').property('club.name') works without explicit createAlias.\n\nCo-Authored-By: Oz <oz-agent@warp.dev>
Add tests for join(String, JoinType) with get(), multiple projections on joined associations, projection via criteria projections block, and invalid property path handling. Assisted-by: Claude Code <Claude@Claude.ai>
* Adopt AntBuilder groovydoc with javaVersion support Replace Gradle's built-in Groovydoc task execution with AntBuilder to support the javaVersion parameter introduced in Groovy 4.0.27 (GROOVY-11668). This is needed because Gradle's Groovydoc task does not expose javaVersion (gradle/gradle#33659 is not merged), causing Java 17+ source parsing failures. Changes across all groovydoc configurations: - gradle/docs-dependencies.gradle: central config for ~90 modules and both aggregate tasks (aggregateGroovydoc, aggregateDataMappingGroovydoc) - gradle/docs-config.gradle: per-module source directory setup - grails-doc/build.gradle: aggregate task source directories - grails-data-docs/stage/build.gradle: data mapping aggregate source dirs - grails-gradle/gradle/docs-config.gradle: independent AntBuilder setup - grails-data-hibernate5/docs/build.gradle: added groovy-ant dependency and AntBuilder execution - grails-data-mongodb/docs/build.gradle: added groovy-ant dependency and AntBuilder execution - grails-forge/gradle/doc-config.gradle: AntBuilder without javaVersion (forge uses Groovy 3.0.25 which predates the feature) Closes #15385 Assisted-by: Claude Code <Claude@Claude.ai> * Centralize groovydoc configuration into GrailsGroovydocPlugin convention plugin Move duplicated AntBuilder groovydoc execution, Matomo footer, documentation configuration registration, and task defaults into a shared convention plugin in build-logic. This eliminates ~490 lines of duplicated configuration across 8 build scripts while maintaining identical behavior. The plugin provides: - Documentation configuration registration with standard attributes - Common Groovydoc task defaults (author, timestamps, scripts) - AntBuilder-based execution with javaVersion support (Groovy 4.0.27+) - Matomo analytics footer - Source directory resolution from ext.groovydocSourceDirs or source sets - External documentation link support via ext.groovydocLinks - GrailsGroovydocExtension for per-project javaVersion control Build scripts retain project-specific configuration: dependencies, titles, source directories for aggregate tasks, and dynamic link resolution. Assisted-by: Claude Code <Claude@Claude.ai> * Split groovydoc plugin into generic base and Grails layer Separate GrailsGroovydocPlugin into a generic GroovydocEnhancerPlugin (with GroovydocEnhancerExtension) and a thin GrailsGroovydocPlugin that applies the base plugin and sets the Matomo footer. This makes the core AntBuilder groovydoc logic publishable and reusable by anyone, while keeping Grails-specific customizations in their own layer. The base plugin supports a useAntBuilder flag so projects can easily switch back to Gradle's built-in Groovydoc task if Gradle merges their javaVersion support (gradle/gradle#33659). Also fix a pre-existing bug in resolveProjectVersion() in docs-dependencies.gradle where the function never returned the resolved version string, causing all external groovydoc links (geb, testcontainers, spring, spring-boot) to be silently omitted. Assisted-by: Claude Code <Claude@Claude.ai> * style: simplify groovydoc plugin * fix: use lazy provider for javaVersion convention Wrap the javaVersion convention in project.provider so the property is resolved at execution time rather than configuration time. Assisted-by: Claude Code <Claude@Claude.ai> * fix: use gdoc.groovyClasspath instead of redundant configuration lookup Use the task's groovyClasspath property directly instead of looking up the 'documentation' configuration a second time, since it was already assigned in configureGroovydocDefaults. Assisted-by: OpenCode <opencode@opencode.ai> * fix: fail groovydoc task instead of silently skipping when sources or classpath are missing Maven Central requires a groovydoc jar for every published module. Silently skipping when source directories or groovyClasspath are empty masks configuration errors that would prevent a valid release. Throw GradleException to surface these issues early. Assisted-by: OpenCode <opencode@opencode.ai> * fix: require javaVersion property instead of defaulting to 17 Remove the silent '17' fallback so a missing javaVersion property in gradle.properties fails the build with a clear error message rather than producing potentially incorrect groovydoc output. Assisted-by: Claude Code <Claude@Claude.ai> --------- Co-authored-by: Mattias Reichel <mattias.reichel@gmail.com>
The neo4j ValidationSpec test was not meaningfully changed by this refactoring, so revert it to the original GormEnhancer.findValidationApi usage as requested by reviewer. Assisted-by: Claude Code <Claude@Claude.ai>
populateHibernateDetachedCriteria only copied criteria and projections
from a DetachedCriteria when converting to a Hibernate DetachedCriteria
for subqueries, ignoring fetchStrategies and joinTypes. This caused
LEFT JOIN to be silently downgraded to INNER JOIN in subqueries like:
Author.where { 'in'('id', Author.where {
join('books', JoinType.LEFT)
books { isNull('name') }
}.id()) }
Now applies fetchStrategies/joinTypes to the HibernateQuery before
processing criteria, matching the pattern in DynamicFinder.applyDetachedCriteria.
Assisted-by: Claude Code <Claude@Claude.ai>
Fixes #14485
Handle composite ID component properties in AbstractHibernateCriteriaBuilder that are not registered as JPA Metamodel attributes. When entityType.getAttribute() fails for a composite key component, fall back to checking if the property type is a managed entity so the criteria builder can navigate associations forming part of a composite key. Assisted-by: Claude Code <Claude@Claude.ai>
Fix import ordering in HibernateCriteriaBuilder.java to satisfy checkstyle ImportOrder rule. Remove unused WqCase, WqEvent, WqPerson entities and unused DetachedCriteria import from WhereQueryBugFixSpec. Assisted-by: Claude Code <Claude@Claude.ai>
…e-fix BUG 7.0.x - Fixes prioritization of a non-namespaced controller when no namespace is defined
Address reviewer feedback:
- Wrap the original IllegalArgumentException with a descriptive message
and cause chain instead of bare rethrowing, so the stack trace shows
where the failure was diagnosed
- Add test for eq('author', author) on composite ID component
Assisted-by: Claude Code <Claude@Claude.ai>
Discourage section separator comments like '// --- Domain classes ---' in source files. These add visual noise without value and are a common AI-generated pattern. Assisted-by: Claude Code <Claude@Claude.ai>
Add no-grouping-comments rule to AGENTS.md
Verify that eq() on a composite ID component works when the argument is an uninitialized Hibernate proxy (via load()) rather than a fully loaded entity. Assisted-by: Claude Code <Claude@Claude.ai>
Fix composite ID criteria projection regression (#14516)
fix: propagate LEFT JOIN from DetachedCriteria into subqueries (#14485)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.