Skip to content

Merge 7.0.x into 7.1.x#15468

Merged
codeconsole merged 80 commits into7.1.xfrom
7.0.x
Feb 26, 2026
Merged

Merge 7.0.x into 7.1.x#15468
codeconsole merged 80 commits into7.1.xfrom
7.0.x

Conversation

@codeconsole
Copy link
Copy Markdown
Contributor

No description provided.

jamesfredley and others added 30 commits February 20, 2026 13:02
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>
matrei and others added 27 commits February 25, 2026 15:47
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)
@codeconsole codeconsole merged commit 164063b into 7.1.x Feb 26, 2026
67 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

5 participants