jamesfredley opened a new pull request, #15393: URL: https://github.com/apache/grails-core/pull/15393
## Expected Behavior When a domain class implements `MultiTenant` AND declares an explicit non-default datasource via `datasource 'secondary'` in its `static mapping` block, `GormEnhancer.allQualifiers()` should preserve the explicit datasource qualifier. GORM operations (`.save()`, `.get()`, `.count()`, etc.) should route to the declared datasource. ## Actual Behaviour `allQualifiers()` unconditionally clears the explicit datasource qualifier for any `MultiTenant` entity and replaces it with `ConnectionSource.DEFAULT` plus all known connection sources. This causes all GORM operations to silently route to the **default datasource** instead of the declared one. For DISCRIMINATOR multi-tenancy mode, `findTenantId()` returns `ConnectionSource.DEFAULT`, so `findStaticApi()` resolves to the DEFAULT qualifier — routing data to the wrong database with no error or warning. ## Steps To Reproduce 1. Create a Grails 7 app with two H2 datasources (default + secondary) 2. Configure DISCRIMINATOR multi-tenancy 3. Create a domain class that implements `MultiTenant` with `datasource 'secondary'` 4. Call `.save()` on an instance of that domain class 5. Observe that data is written to the default database, not the secondary one **Example application**: https://github.com/jamesfredley/grails-multitenant-datasource-bug ## Root Cause In `GormEnhancer.groovy` lines 180-193, the `allQualifiers()` method: ```groovy if ((MultiTenant.isAssignableFrom(entity.javaClass) || qualifiers.contains(ConnectionSource.ALL)) && (datastore instanceof ConnectionSourcesProvider)) { qualifiers.clear() // ← CLEARS the explicit 'secondary' mapping! qualifiers.add(ConnectionSource.DEFAULT) // ← Replaces with DEFAULT // adds all connection sources from ConnectionSourcesProvider } ``` The `MultiTenant` check unconditionally triggers qualifier expansion, treating `MultiTenant` the same as `ConnectionSource.ALL`. This is intended for DATABASE multi-tenancy mode (each tenant = separate connection), but incorrectly fires for DISCRIMINATOR mode where tenants share one DB. ## Fix The fix adds a check: when a `MultiTenant` entity has an explicit non-default, non-ALL datasource declaration, the explicit qualifiers are preserved instead of being cleared. The qualifier expansion only fires for: - `MultiTenant` entities on the default datasource (DATABASE multi-tenancy) - Entities declared with `ConnectionSource.ALL` **7 Spock tests added** covering all combinations of MultiTenant/non-MultiTenant × explicit/default/ALL datasource declarations. ## Environment Information - Grails: 7.0.7 - Spring Boot: 3.5.10 - Groovy: 4.0.30 - GORM: 7.1 (grails-datamapping-core) - JDK: 25 ## Version 7.0.7 -- 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]
