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]