matrei opened a new pull request, #16703:
URL: https://github.com/apache/grails-core/pull/16703

   ## Description
   
   With `DISCRIMINATOR` multi-tenancy, only `Tenants.withoutId` and 
`@WithoutTenant` now run without a tenant. Running without a tenant is a state 
that `withoutId` binds, instead of something GORM derives from the value of the 
tenant id. A tenant id of `DEFAULT`, the name of the default connection source, 
is therefore a tenant id like any other, whether a `TenantResolver` returns it 
or it is passed to `Tenants.withId`.
   
   GORM now also handles these cases the same way as a query on a multi-tenant 
entity:
   
   - A `TenantResolver` that returns `null` causes a `TenantNotFoundException`, 
as a resolver that finds no tenant does.
   - GORM for Hibernate restricts a collection of a multi-tenant entity to the 
current tenant whether it is loaded lazily or fetched by join. That includes 
many-to-many collections and collections that a query fetches by join through 
another association. `load` and `proxy` of a multi-tenant entity run a query, 
as `get` does.
   
   ### Running without a tenant
   
   - `Tenants.withoutId` binds a private marker that equals 
`ConnectionSource.DEFAULT`. The marker never leaves `Tenants`. Inside 
`withoutId`, `Tenants.currentId()` returns `DEFAULT`, so code that uses the 
current tenant id as the name of a connection source keeps working.
   - The new `Tenants.isWithoutId()` and `Tenants.isWithoutId(Datastore)` tell 
whether the current code runs without a tenant id. The multi-tenant event 
listeners and the Hibernate tenant filter use them instead of comparing the 
tenant id with `DEFAULT`.
   - The new `Tenants.withCurrent(MultiTenantCapableDatastore, Closure)` is 
used by both `withCurrent` overloads and by `DefaultTenantService.withCurrent`. 
Inside `withoutId`, `withCurrent` and `@CurrentTenant` therefore keep running 
without a tenant, and they pass `DEFAULT` to a closure that takes the tenant id.
   - `Tenants.currentId` throws `TenantNotFoundException` when the resolver 
returns `null`.
   
   ### GORM for Hibernate 5
   
   - `enableMultiTenancyFilter()` leaves the tenant filter off only inside 
`withoutId`. Otherwise it filters on the current tenant id, including `DEFAULT`.
   - The new `MultiTenantCollectionEventListener` sets the filter before 
Hibernate initializes a collection of a multi-tenant entity. Lazy collections 
are therefore restricted in a fresh session and after `withoutId` as well.
   - `GrailsDomainBinder` adds the tenant filter to a many-to-many collection 
of a multi-tenant entity. A filtered collection mapped `fetch: 'join'` is 
loaded with a select of its own, because Hibernate doesn't apply the filter to 
a collection that it fetches by join together with an instance loaded by id.
   - The new `enableMultiTenancyFilter(PersistentEntity)` handles queries on an 
entity that isn't multi-tenant but has a collection of a multi-tenant entity, 
directly or through its associations. Before such a query, it sets the filter 
when there is a current tenant. The listener calls it on every `PreQueryEvent`, 
and the criteria builder calls it when it is created. That covers `join fetch` 
in HQL and `fetchMode` in criteria.
   - While GORM resolves the tenant for the filter, a query on such an entity 
doesn't call the resolver again. A resolver can therefore look the tenant up 
with GORM, for example with `Account.findByHost(host)`.
   - `load` and `proxy` of a multi-tenant entity run `get`.
   
   ### Documentation
   
   - Upgrade note 19 describes the changes and what to do about them.
   - The discriminator multi-tenancy section of the GORM for Hibernate guide 
covers:
     - working with every tenant through `withoutId` and `@WithoutTenant`;
     - collections, `load` and `proxy`;
     - the resolver call before a query on an entity that has a collection of a 
multi-tenant entity.
   - The partitioned multi-tenancy section of the GORM for MongoDB guide 
explains how GORM sets the tenant id on save.
   
   ### Tests
   
   - `PartitionedMultiTenancyWithoutIdSpec`, in `grails-datamapping-core-test` 
(in-memory datastore) and in `grails-data-hibernate5-core` (H2), covers:
     - `isWithoutId`;
     - `DEFAULT` from a resolver and from `withId` as an ordinary tenant for 
queries, `get`, inserts and updates;
     - nesting with `withId` and `withCurrent`;
     - `@CurrentTenant` and `@WithoutTenant` services;
     - saving inside `withoutId`.
   - `PartitionedMultiTenancyNullTenantSpec` (`grails-datamapping-core-test`) 
checks that a resolver that returns `null` fails `currentId`, queries and 
saves, and that `withoutId` still works.
   - `PartitionedMultiTenancyFilterSpec` (`grails-data-hibernate5-core`) covers:
     - lazy, join-fetched and many-to-many collections;
     - `join fetch` in HQL and `fetchMode` in criteria, also through another 
association;
     - `load` and `proxy`;
     - a resolver that returns `null`.
   - `PartitionedMultiTenancyResolverQuerySpec` (`grails-data-hibernate5-core`) 
covers a resolver that queries with GORM, and an exception from the resolver.
   
   These pass with the change:
   
   - all tests of `grails-datamapping-core`, `grails-datamapping-core-test`, 
`grails-data-hibernate5-core` and `grails-datastore-web`;
   - the multi-tenancy specs of `grails-data-mongodb-core`, including the TCK's 
multi-tenant specs;
   - `codeStyle` for `grails-datamapping-core`, `grails-data-hibernate5-core` 
and `grails-data-mongodb-core`.
   
   ### Related
   
   #16515 adds a `Tenants.isWithoutId()` with the same signature, which 
compares the bound tenant id with `DEFAULT`. If this PR is merged first, #16515 
can rebase onto it, drop its own `isWithoutId` and renumber its upgrade note to 
20.
   
   The 8.0.x counterpart follows in a separate PR.
   


-- 
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]

Reply via email to