@mikro-orm/core 7.2.0-dev.2 → 7.2.0-dev.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/EntityManager.d.ts +42 -8
- package/EntityManager.js +256 -70
- package/MikroORM.d.ts +4 -0
- package/MikroORM.js +9 -0
- package/cache/CacheAdapter.d.ts +6 -4
- package/cache/FileCacheAdapter.d.ts +1 -1
- package/cache/FileCacheAdapter.js +7 -2
- package/connections/Connection.d.ts +10 -1
- package/connections/Connection.js +9 -0
- package/drivers/DatabaseDriver.d.ts +17 -1
- package/drivers/DatabaseDriver.js +203 -41
- package/drivers/IDatabaseDriver.d.ts +1 -0
- package/entity/Collection.js +4 -2
- package/entity/EntityFactory.js +6 -0
- package/entity/EntityLoader.d.ts +7 -1
- package/entity/EntityLoader.js +46 -11
- package/entity/EntityRepository.d.ts +4 -5
- package/entity/EntityRepository.js +7 -2
- package/entity/defineEntity.d.ts +48 -14
- package/entity/defineEntity.js +32 -1
- package/enums.d.ts +5 -1
- package/enums.js +2 -0
- package/errors.d.ts +36 -0
- package/errors.js +90 -0
- package/events/EventManager.js +6 -3
- package/exceptions.d.ts +5 -0
- package/exceptions.js +5 -0
- package/hydration/ObjectHydrator.d.ts +2 -0
- package/hydration/ObjectHydrator.js +15 -8
- package/index.d.ts +1 -1
- package/metadata/EntitySchema.js +5 -2
- package/metadata/MetadataDiscovery.d.ts +3 -0
- package/metadata/MetadataDiscovery.js +161 -28
- package/metadata/MetadataProvider.js +1 -1
- package/metadata/MetadataStorage.d.ts +4 -3
- package/metadata/MetadataStorage.js +32 -2
- package/metadata/Routine.js +4 -1
- package/metadata/types.d.ts +19 -3
- package/naming-strategy/AbstractNamingStrategy.js +2 -1
- package/naming-strategy/NamingStrategy.d.ts +2 -1
- package/package.json +1 -1
- package/platforms/Platform.d.ts +24 -3
- package/platforms/Platform.js +63 -1
- package/types/BigIntType.d.ts +1 -0
- package/types/BigIntType.js +23 -0
- package/types/DateTimeType.d.ts +1 -0
- package/types/DateTimeType.js +8 -0
- package/types/StringType.d.ts +14 -3
- package/types/StringType.js +34 -4
- package/types/TextType.d.ts +2 -4
- package/types/TextType.js +2 -8
- package/types/Type.d.ts +11 -0
- package/types/Type.js +4 -4
- package/types/index.d.ts +2 -2
- package/typings.d.ts +58 -2
- package/typings.js +24 -1
- package/unit-of-work/ChangeSet.js +10 -7
- package/unit-of-work/ChangeSetPersister.js +32 -20
- package/unit-of-work/UnitOfWork.js +11 -4
- package/utils/AbstractMigrator.d.ts +1 -1
- package/utils/AbstractMigrator.js +3 -2
- package/utils/Configuration.d.ts +15 -1
- package/utils/Configuration.js +13 -2
- package/utils/Cursor.d.ts +2 -0
- package/utils/Cursor.js +44 -39
- package/utils/DataloaderUtils.js +2 -1
- package/utils/EntityComparator.d.ts +2 -0
- package/utils/EntityComparator.js +11 -4
- package/utils/QueryHelper.d.ts +17 -0
- package/utils/QueryHelper.js +100 -4
- package/utils/RawQueryFragment.d.ts +6 -0
- package/utils/RawQueryFragment.js +15 -6
- package/utils/RequestContext.d.ts +2 -2
- package/utils/RequestContext.js +11 -2
- package/utils/TransactionManager.d.ts +6 -0
- package/utils/TransactionManager.js +36 -3
- package/utils/Utils.d.ts +14 -2
- package/utils/Utils.js +36 -6
- package/utils/clone.js +6 -0
- package/utils/env-vars.js +1 -0
- package/utils/index.d.ts +1 -0
- package/utils/index.js +1 -0
- package/utils/rls-utils.d.ts +35 -0
- package/utils/rls-utils.js +97 -0
- package/utils/upsert-utils.d.ts +9 -1
- package/utils/upsert-utils.js +26 -3
package/EntityManager.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { getOnConflictReturningFields, getWhereCondition, resetUntouchedCollections } from './utils/upsert-utils.js';
|
|
1
|
+
import { getOnConflictReturningFields, getOnCreateGeneratedFields, getWhereCondition, resetUntouchedCollections, } from './utils/upsert-utils.js';
|
|
2
|
+
import { computeRemovedRlsVariables, computeRlsFilterVariables, findRlsFilterDefs } from './utils/rls-utils.js';
|
|
2
3
|
import { Utils } from './utils/Utils.js';
|
|
3
4
|
import { Cursor } from './utils/Cursor.js';
|
|
4
5
|
import { QueryHelper } from './utils/QueryHelper.js';
|
|
@@ -15,7 +16,7 @@ import { UnitOfWork } from './unit-of-work/UnitOfWork.js';
|
|
|
15
16
|
import { EventType, FlushMode, LoadStrategy, LockMode, PopulateHint, PopulatePath, QueryFlag, ReferenceKind, SCALAR_TYPES, } from './enums.js';
|
|
16
17
|
import { EventManager } from './events/EventManager.js';
|
|
17
18
|
import { TransactionEventBroadcaster } from './events/TransactionEventBroadcaster.js';
|
|
18
|
-
import { OptimisticLockError, ValidationError } from './errors.js';
|
|
19
|
+
import { MetadataError, OptimisticLockError, ValidationError } from './errors.js';
|
|
19
20
|
import { applyPopulateHints, getLoadingStrategy } from './entity/utils.js';
|
|
20
21
|
import { TransactionManager } from './utils/TransactionManager.js';
|
|
21
22
|
/**
|
|
@@ -44,6 +45,7 @@ export class EntityManager {
|
|
|
44
45
|
#resultCache;
|
|
45
46
|
#filters = {};
|
|
46
47
|
#filterParams = {};
|
|
48
|
+
#sessionContext;
|
|
47
49
|
loggerContext;
|
|
48
50
|
#transactionContext;
|
|
49
51
|
#disableTransactions;
|
|
@@ -116,7 +118,7 @@ export class EntityManager {
|
|
|
116
118
|
return ret;
|
|
117
119
|
}
|
|
118
120
|
const em = this.getContext();
|
|
119
|
-
em.prepareOptions(options);
|
|
121
|
+
options = em.prepareOptions(options);
|
|
120
122
|
const meta = this.metadata.get(entityName);
|
|
121
123
|
em.validateIndexUsage(meta, where, options);
|
|
122
124
|
await em.tryFlush(entityName, options);
|
|
@@ -199,7 +201,12 @@ export class EntityManager {
|
|
|
199
201
|
*/
|
|
200
202
|
async *stream(entityName, options = {}) {
|
|
201
203
|
const em = this.getContext();
|
|
202
|
-
|
|
204
|
+
// a stream never opens the implicit session-context transaction, so under the 'transaction' strategy the staged
|
|
205
|
+
// context would silently never apply outside an ambient transaction and other tenants' rows would leak — fail closed
|
|
206
|
+
if (!options.ctx && !em.#transactionContext && em.getTransactionSessionContext()) {
|
|
207
|
+
throw ValidationError.sessionContextStreamRequiresTransaction();
|
|
208
|
+
}
|
|
209
|
+
options = em.prepareOptions(options);
|
|
203
210
|
options.strategy = 'joined';
|
|
204
211
|
await em.tryFlush(entityName, options);
|
|
205
212
|
const where = (await em.processWhere(entityName, options.where ?? {}, options, 'read'));
|
|
@@ -258,8 +265,17 @@ export class EntityManager {
|
|
|
258
265
|
* Registers global filter to this entity manager. Global filters are enabled by default (unless disabled via last parameter).
|
|
259
266
|
*/
|
|
260
267
|
addFilter(options) {
|
|
268
|
+
options = { ...options };
|
|
261
269
|
if (options.entity) {
|
|
262
|
-
options.entity = Utils.asArray(options.entity).map(n => Utils.
|
|
270
|
+
options.entity = Utils.asArray(options.entity).map(n => Utils.classOrName(n));
|
|
271
|
+
}
|
|
272
|
+
// runtime-registered filters are never part of entity metadata, so no policy can be generated for them — whether or
|
|
273
|
+
// not an `entity` was scoped, `rls` is only valid on filters declared in metadata via `@Filter()`. `rls` is excluded
|
|
274
|
+
// from the type above so TS users fail at compile time; the runtime guard still covers JS callers using `as any`
|
|
275
|
+
if (options.rls) {
|
|
276
|
+
throw options.entity
|
|
277
|
+
? MetadataError.rlsFilterCannotBeRegisteredAtRuntime(options.name)
|
|
278
|
+
: MetadataError.rlsFilterMustBeEntityScoped(options.name);
|
|
263
279
|
}
|
|
264
280
|
options.default ??= true;
|
|
265
281
|
this.getContext(false).#filters[options.name] = options;
|
|
@@ -269,7 +285,37 @@ export class EntityManager {
|
|
|
269
285
|
* If you want to set shared value for all contexts, be sure to use the root entity manager.
|
|
270
286
|
*/
|
|
271
287
|
setFilterParams(name, args) {
|
|
272
|
-
this.getContext()
|
|
288
|
+
const em = this.getContext();
|
|
289
|
+
// `rls` filters mirror their params as session variables, so the matching DB policies see the same values;
|
|
290
|
+
// the same filter name can be declared on multiple entities, so stage the union across all `rls`-flagged defs
|
|
291
|
+
const filters = findRlsFilterDefs(em.metadata, name);
|
|
292
|
+
if (filters.length === 0) {
|
|
293
|
+
em.#filterParams[name] = args;
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
// fail before storing the params and pruning stale variables below, so an invalid staging attempt leaves both
|
|
297
|
+
// the filter params and the session context untouched
|
|
298
|
+
em.validateSessionContextStaging();
|
|
299
|
+
const variables = computeRlsFilterVariables(filters, args);
|
|
300
|
+
const previousArgs = em.#filterParams[name];
|
|
301
|
+
em.#filterParams[name] = args;
|
|
302
|
+
// this call replaces the filter's params, so drop the exact variables a previous call for this filter staged but
|
|
303
|
+
// this one no longer sets (unless another filter's current params still stage them)
|
|
304
|
+
const staged = em.#sessionContext?.variables;
|
|
305
|
+
if (staged && previousArgs) {
|
|
306
|
+
for (const key of computeRemovedRlsVariables(em.metadata, name, filters, previousArgs, variables, em.#filterParams)) {
|
|
307
|
+
delete staged[key];
|
|
308
|
+
}
|
|
309
|
+
// pruning may have emptied the whole context — drop it, so it does not keep forcing the implicit
|
|
310
|
+
// transaction wrap (and a distinct cache key) while carrying no variables
|
|
311
|
+
if (Object.keys(staged).length === 0 && !em.#sessionContext.role) {
|
|
312
|
+
em.#sessionContext = undefined;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
// an empty context would still switch on the implicit transaction wrapping
|
|
316
|
+
if (Object.keys(variables).length > 0) {
|
|
317
|
+
em.mergeSessionContext({ variables });
|
|
318
|
+
}
|
|
273
319
|
}
|
|
274
320
|
/**
|
|
275
321
|
* Returns filter parameters for given filter set in this context.
|
|
@@ -277,6 +323,90 @@ export class EntityManager {
|
|
|
277
323
|
getFilterParams(name) {
|
|
278
324
|
return this.getContext().#filterParams[name];
|
|
279
325
|
}
|
|
326
|
+
/**
|
|
327
|
+
* Sets the database session context (row level security) for this entity manager. Session variables are merged
|
|
328
|
+
* with any previously set ones, while the role is replaced when one is provided. The variables are applied via `set_config()` and are
|
|
329
|
+
* typically referenced by RLS policies through `current_setting()`.
|
|
330
|
+
*/
|
|
331
|
+
setSessionContext(context) {
|
|
332
|
+
// validate the global context like `setFilterParams` — a tenant context set on the global EM
|
|
333
|
+
// would be silently inherited by every later fork
|
|
334
|
+
this.getContext().mergeSessionContext(context);
|
|
335
|
+
}
|
|
336
|
+
validateSessionContextStaging() {
|
|
337
|
+
if (!this.getPlatform().supportsRowLevelSecurity()) {
|
|
338
|
+
throw ValidationError.sessionContextNotSupported();
|
|
339
|
+
}
|
|
340
|
+
// staging inside an open transaction is inert under both strategies (the 'transaction' context is only emitted at
|
|
341
|
+
// top-level begin; the 'connection' context was applied when the pinned connection was reserved), while
|
|
342
|
+
// `getSessionContext()` would still claim it is set — fail closed regardless of strategy
|
|
343
|
+
if (this.#transactionContext) {
|
|
344
|
+
throw ValidationError.sessionContextInsideTransaction();
|
|
345
|
+
}
|
|
346
|
+
if (this.config.get('sessionContext') === 'transaction') {
|
|
347
|
+
// the context is applied on transaction begin, so without implicit transactions writes run untransacted and
|
|
348
|
+
// silently bypass the policies — fail closed instead of leaking a base-role write
|
|
349
|
+
if (this.config.get('implicitTransactions') === false) {
|
|
350
|
+
throw ValidationError.sessionContextRequiresImplicitTransactions();
|
|
351
|
+
}
|
|
352
|
+
// same rationale for disabled transactions (config or fork option): the UoW flush would run untransacted
|
|
353
|
+
// and skip the context while reads still get the per-statement wrap — fail closed on the asymmetry
|
|
354
|
+
if (this.#disableTransactions) {
|
|
355
|
+
throw ValidationError.sessionContextWithDisabledTransactions();
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
/** Merges into this exact instance — `fork()` must bypass context resolution to target the new fork. */
|
|
360
|
+
mergeSessionContext(context) {
|
|
361
|
+
this.validateSessionContextStaging();
|
|
362
|
+
const current = this.#sessionContext;
|
|
363
|
+
const merged = {
|
|
364
|
+
variables: { ...current?.variables, ...context.variables },
|
|
365
|
+
role: context.role ?? current?.role,
|
|
366
|
+
};
|
|
367
|
+
// normalize an empty context away — it would still force the implicit transaction wrap and a distinct cache key
|
|
368
|
+
this.#sessionContext = Object.keys(merged.variables).length > 0 || merged.role ? merged : undefined;
|
|
369
|
+
}
|
|
370
|
+
/**
|
|
371
|
+
* Returns the database session context (row level security) set for this entity manager, or `undefined` if none.
|
|
372
|
+
*/
|
|
373
|
+
getSessionContext() {
|
|
374
|
+
return this.getContext(false).#sessionContext;
|
|
375
|
+
}
|
|
376
|
+
/**
|
|
377
|
+
* Clears the database session context, since `setSessionContext()` only ever merges variables and updates the role.
|
|
378
|
+
*/
|
|
379
|
+
clearSessionContext() {
|
|
380
|
+
const em = this.getContext(false);
|
|
381
|
+
// clearing inside an open transaction would be as inert (and cache-poisoning) as staging there — fail closed too,
|
|
382
|
+
// under both strategies (the 'connection' pinned connection was already reserved with the previous context)
|
|
383
|
+
if (em.#sessionContext && em.#transactionContext) {
|
|
384
|
+
throw ValidationError.sessionContextInsideTransaction('clear');
|
|
385
|
+
}
|
|
386
|
+
em.#sessionContext = undefined;
|
|
387
|
+
}
|
|
388
|
+
/** @internal session context to apply on `begin()` under the `'transaction'` strategy (`undefined` otherwise). */
|
|
389
|
+
getTransactionSessionContext() {
|
|
390
|
+
const em = this.getContext(false);
|
|
391
|
+
if (!em.#sessionContext || em.config.get('sessionContext') !== 'transaction') {
|
|
392
|
+
return undefined;
|
|
393
|
+
}
|
|
394
|
+
return em.#sessionContext;
|
|
395
|
+
}
|
|
396
|
+
/**
|
|
397
|
+
* Wraps a driver call in a short implicit transaction when the session context needs to apply, so `set local`
|
|
398
|
+
* takes effect. Resolves to a plain call when already inside a transaction or when no session context is set.
|
|
399
|
+
*
|
|
400
|
+
* @internal
|
|
401
|
+
*/
|
|
402
|
+
async withSessionContext(ctx, cb) {
|
|
403
|
+
const em = this.getContext(false);
|
|
404
|
+
const sessionContext = ctx ? undefined : em.getTransactionSessionContext();
|
|
405
|
+
if (!sessionContext) {
|
|
406
|
+
return cb(ctx);
|
|
407
|
+
}
|
|
408
|
+
return em.getConnection('write').transactional(trx => cb(trx), { sessionContext, loggerContext: em.loggerContext });
|
|
409
|
+
}
|
|
280
410
|
/**
|
|
281
411
|
* Sets logger context for this entity manager.
|
|
282
412
|
*/
|
|
@@ -382,6 +512,7 @@ export class EntityManager {
|
|
|
382
512
|
filters,
|
|
383
513
|
populate: hint.children,
|
|
384
514
|
populateWhere: PopulateHint.ALL,
|
|
515
|
+
populateFilter: undefined,
|
|
385
516
|
});
|
|
386
517
|
if (Utils.hasObjectKeys(where)) {
|
|
387
518
|
ret[field] = ret[field] ? { $and: [where, ret[field]] } : where;
|
|
@@ -528,7 +659,7 @@ export class EntityManager {
|
|
|
528
659
|
async findAndCount(entityName, where, options = {}) {
|
|
529
660
|
const em = this.getContext(false);
|
|
530
661
|
await em.tryFlush(entityName, options);
|
|
531
|
-
options
|
|
662
|
+
options = { ...options, flushMode: 'commit' }; // do not try to auto flush again
|
|
532
663
|
return Promise.all([
|
|
533
664
|
em.find(entityName, where, options),
|
|
534
665
|
em.count(entityName, where, options),
|
|
@@ -592,6 +723,7 @@ export class EntityManager {
|
|
|
592
723
|
*/
|
|
593
724
|
async findByCursor(entityName, options) {
|
|
594
725
|
const em = this.getContext(false);
|
|
726
|
+
options = { ...options };
|
|
595
727
|
options.overfetch ??= true;
|
|
596
728
|
options.where ??= {};
|
|
597
729
|
if (Utils.isEmpty(options.orderBy) && !Raw.hasObjectFragments(options.orderBy)) {
|
|
@@ -610,10 +742,10 @@ export class EntityManager {
|
|
|
610
742
|
async refreshOrFail(entity, options = {}) {
|
|
611
743
|
const ret = await this.refresh(entity, options);
|
|
612
744
|
if (!ret) {
|
|
613
|
-
options.failHandler
|
|
745
|
+
const failHandler = options.failHandler ?? this.config.get('findOneOrFailHandler');
|
|
614
746
|
const wrapped = helper(entity);
|
|
615
747
|
const where = wrapped.getPrimaryKey();
|
|
616
|
-
throw
|
|
748
|
+
throw failHandler(wrapped.__meta.className, where);
|
|
617
749
|
}
|
|
618
750
|
return ret;
|
|
619
751
|
}
|
|
@@ -667,7 +799,7 @@ export class EntityManager {
|
|
|
667
799
|
return ret;
|
|
668
800
|
}
|
|
669
801
|
const em = this.getContext();
|
|
670
|
-
em.prepareOptions(options);
|
|
802
|
+
options = em.prepareOptions(options);
|
|
671
803
|
let entity = em.#unitOfWork.tryGetById(entityName, where, options.schema);
|
|
672
804
|
// query for a not managed entity which is already in the identity map as it
|
|
673
805
|
// was provided with a PK this entity does not exist in the db, there can't
|
|
@@ -739,11 +871,11 @@ export class EntityManager {
|
|
|
739
871
|
}
|
|
740
872
|
if (!entity || isStrictViolation) {
|
|
741
873
|
const key = options.strict ? 'findExactlyOneOrFailHandler' : 'findOneOrFailHandler';
|
|
742
|
-
options.failHandler
|
|
874
|
+
const failHandler = options.failHandler ?? this.config.get(key);
|
|
743
875
|
const name = Utils.className(entityName);
|
|
744
876
|
/* v8 ignore next */
|
|
745
877
|
where = Utils.isEntity(where) ? helper(where).getPrimaryKey() : where;
|
|
746
|
-
throw
|
|
878
|
+
throw failHandler(name, where);
|
|
747
879
|
}
|
|
748
880
|
return entity;
|
|
749
881
|
}
|
|
@@ -778,7 +910,7 @@ export class EntityManager {
|
|
|
778
910
|
return ret;
|
|
779
911
|
}
|
|
780
912
|
const em = this.getContext(false);
|
|
781
|
-
em.prepareOptions(options);
|
|
913
|
+
options = em.prepareOptions(options);
|
|
782
914
|
let entityName;
|
|
783
915
|
let where;
|
|
784
916
|
let entity = null;
|
|
@@ -791,6 +923,7 @@ export class EntityManager {
|
|
|
791
923
|
}
|
|
792
924
|
const meta = this.metadata.get(entityName);
|
|
793
925
|
const convertCustomTypes = !Utils.isEntity(data);
|
|
926
|
+
let generatedFields = [];
|
|
794
927
|
if (Utils.isEntity(data)) {
|
|
795
928
|
entity = data;
|
|
796
929
|
if (helper(entity).__managed && helper(entity).__em === em && !this.config.get('upsertManaged')) {
|
|
@@ -798,6 +931,7 @@ export class EntityManager {
|
|
|
798
931
|
return entity;
|
|
799
932
|
}
|
|
800
933
|
where = helper(entity).getPrimaryKey();
|
|
934
|
+
generatedFields = getOnCreateGeneratedFields(meta, entity);
|
|
801
935
|
em.#entityFactory.assignDefaultValues(entity, meta);
|
|
802
936
|
data = em.#comparator.prepareEntity(entity);
|
|
803
937
|
}
|
|
@@ -810,6 +944,7 @@ export class EntityManager {
|
|
|
810
944
|
return em.assign(exists, data);
|
|
811
945
|
}
|
|
812
946
|
}
|
|
947
|
+
generatedFields = getOnCreateGeneratedFields(meta, data);
|
|
813
948
|
em.#entityFactory.assignDefaultValues(data, meta, true);
|
|
814
949
|
for (const key of Object.keys(data)) {
|
|
815
950
|
const prop = meta.properties[key];
|
|
@@ -818,6 +953,10 @@ export class EntityManager {
|
|
|
818
953
|
}
|
|
819
954
|
}
|
|
820
955
|
}
|
|
956
|
+
// `onCreate` generated values are for the insert clause only, they must not overwrite an existing row
|
|
957
|
+
if (generatedFields.length > 0 && !options.onConflictMergeFields) {
|
|
958
|
+
options.onConflictExcludeFields = [...(options.onConflictExcludeFields ?? []), ...generatedFields];
|
|
959
|
+
}
|
|
821
960
|
where = getWhereCondition(meta, options.onConflictFields, data, where).where;
|
|
822
961
|
data = QueryHelper.processObjectParams(data);
|
|
823
962
|
validateParams(data, 'insert data');
|
|
@@ -827,12 +966,12 @@ export class EntityManager {
|
|
|
827
966
|
data = em.#comparator.prepareEntity(entity);
|
|
828
967
|
}
|
|
829
968
|
}
|
|
830
|
-
const ret = await em.driver.nativeUpdate(entityName, where, data, {
|
|
831
|
-
ctx: em.#transactionContext,
|
|
969
|
+
const ret = await em.withSessionContext(options.ctx ?? em.#transactionContext, ctx => em.driver.nativeUpdate(entityName, where, data, {
|
|
832
970
|
upsert: true,
|
|
833
971
|
convertCustomTypes,
|
|
834
972
|
...options,
|
|
835
|
-
|
|
973
|
+
ctx,
|
|
974
|
+
}));
|
|
836
975
|
em.#unitOfWork.getChangeSetPersister().mapReturnedValues(entity, data, ret.row, meta, true);
|
|
837
976
|
entity ??= em.#entityFactory.create(entityName, data, {
|
|
838
977
|
refresh: true,
|
|
@@ -864,13 +1003,13 @@ export class EntityManager {
|
|
|
864
1003
|
where[meta.primaryKeys[0]] = ret.insertId;
|
|
865
1004
|
}
|
|
866
1005
|
}
|
|
867
|
-
const data2 = await this.driver.findOne(meta.class, where, {
|
|
1006
|
+
const data2 = await em.withSessionContext(options.ctx ?? em.#transactionContext, ctx => this.driver.findOne(meta.class, where, {
|
|
868
1007
|
fields: returning.concat(...(options.onConflictMergeFields ?? [])),
|
|
869
|
-
ctx
|
|
1008
|
+
ctx,
|
|
870
1009
|
convertCustomTypes: true,
|
|
871
1010
|
connectionType: 'write',
|
|
872
1011
|
schema: options.schema,
|
|
873
|
-
});
|
|
1012
|
+
}));
|
|
874
1013
|
em.getHydrator().hydrate(entity, meta, data2, em.#entityFactory, 'full', false, true);
|
|
875
1014
|
}
|
|
876
1015
|
// recompute the data as there might be some values missing (e.g. those with db column defaults)
|
|
@@ -916,7 +1055,7 @@ export class EntityManager {
|
|
|
916
1055
|
return ret;
|
|
917
1056
|
}
|
|
918
1057
|
const em = this.getContext(false);
|
|
919
|
-
em.prepareOptions(options);
|
|
1058
|
+
options = em.prepareOptions(options);
|
|
920
1059
|
let entityName;
|
|
921
1060
|
let propIndex;
|
|
922
1061
|
if (data === undefined) {
|
|
@@ -937,6 +1076,7 @@ export class EntityManager {
|
|
|
937
1076
|
}
|
|
938
1077
|
const meta = this.metadata.get(entityName);
|
|
939
1078
|
const convertCustomTypes = !Utils.isEntity(data[0]);
|
|
1079
|
+
const generatedFields = new Set();
|
|
940
1080
|
const allData = [];
|
|
941
1081
|
const allWhere = [];
|
|
942
1082
|
const entities = new Map();
|
|
@@ -954,6 +1094,7 @@ export class EntityManager {
|
|
|
954
1094
|
continue;
|
|
955
1095
|
}
|
|
956
1096
|
where = helper(entity).getPrimaryKey();
|
|
1097
|
+
getOnCreateGeneratedFields(meta, entity).forEach(field => generatedFields.add(field));
|
|
957
1098
|
em.#entityFactory.assignDefaultValues(entity, meta);
|
|
958
1099
|
entitiesByAllDataIdx.set(allData.length, entity);
|
|
959
1100
|
row = em.#comparator.prepareEntity(entity);
|
|
@@ -970,6 +1111,7 @@ export class EntityManager {
|
|
|
970
1111
|
continue;
|
|
971
1112
|
}
|
|
972
1113
|
}
|
|
1114
|
+
getOnCreateGeneratedFields(meta, row).forEach(field => generatedFields.add(field));
|
|
973
1115
|
em.#entityFactory.assignDefaultValues(row, meta, true);
|
|
974
1116
|
for (const key of Object.keys(row)) {
|
|
975
1117
|
const prop = meta.properties[key];
|
|
@@ -998,6 +1140,10 @@ export class EntityManager {
|
|
|
998
1140
|
if (entities.size === data.length) {
|
|
999
1141
|
return [...entities.keys()];
|
|
1000
1142
|
}
|
|
1143
|
+
// `onCreate` generated values are for the insert clause only, they must not overwrite existing rows
|
|
1144
|
+
if (generatedFields.size > 0 && !options.onConflictMergeFields) {
|
|
1145
|
+
options.onConflictExcludeFields = [...(options.onConflictExcludeFields ?? []), ...generatedFields];
|
|
1146
|
+
}
|
|
1001
1147
|
if (em.eventManager.hasListeners(EventType.beforeUpsert, meta)) {
|
|
1002
1148
|
for (const dto of data) {
|
|
1003
1149
|
const entity = entitiesByData.get(dto) ?? dto;
|
|
@@ -1007,12 +1153,12 @@ export class EntityManager {
|
|
|
1007
1153
|
allData[idx] = em.#comparator.prepareEntity(entity);
|
|
1008
1154
|
}
|
|
1009
1155
|
}
|
|
1010
|
-
const res = await em.driver.nativeUpdateMany(entityName, allWhere, allData, {
|
|
1011
|
-
ctx: em.#transactionContext,
|
|
1156
|
+
const res = await em.withSessionContext(options.ctx ?? em.#transactionContext, ctx => em.driver.nativeUpdateMany(entityName, allWhere, allData, {
|
|
1012
1157
|
upsert: true,
|
|
1013
1158
|
convertCustomTypes,
|
|
1014
1159
|
...options,
|
|
1015
|
-
|
|
1160
|
+
ctx,
|
|
1161
|
+
}));
|
|
1016
1162
|
entities.clear();
|
|
1017
1163
|
entitiesByData.clear();
|
|
1018
1164
|
const loadPK = new Map();
|
|
@@ -1055,16 +1201,16 @@ export class EntityManager {
|
|
|
1055
1201
|
where.$or[idx][prop] = item[prop];
|
|
1056
1202
|
});
|
|
1057
1203
|
});
|
|
1058
|
-
const data2 = await this.driver.find(meta.class, where, {
|
|
1204
|
+
const data2 = await em.withSessionContext(options.ctx ?? em.#transactionContext, ctx => this.driver.find(meta.class, where, {
|
|
1059
1205
|
fields: returning
|
|
1060
1206
|
.concat(...add)
|
|
1061
1207
|
.concat(...(Array.isArray(uniqueFields) ? uniqueFields : []))
|
|
1062
1208
|
.concat(...(options.onConflictMergeFields ?? [])),
|
|
1063
|
-
ctx
|
|
1209
|
+
ctx,
|
|
1064
1210
|
convertCustomTypes: true,
|
|
1065
1211
|
connectionType: 'write',
|
|
1066
1212
|
schema: options.schema,
|
|
1067
|
-
});
|
|
1213
|
+
}));
|
|
1068
1214
|
for (const [entity, cond] of loadPK.entries()) {
|
|
1069
1215
|
const row = data2.find(row => {
|
|
1070
1216
|
const tmp = {};
|
|
@@ -1162,6 +1308,7 @@ export class EntityManager {
|
|
|
1162
1308
|
}
|
|
1163
1309
|
const em = this.getContext(false);
|
|
1164
1310
|
em.#transactionContext = await em.getConnection('write').begin({
|
|
1311
|
+
sessionContext: em.getTransactionSessionContext(),
|
|
1165
1312
|
...options,
|
|
1166
1313
|
eventBroadcaster: new TransactionEventBroadcaster(em, { topLevelTransaction: !options.ctx }),
|
|
1167
1314
|
});
|
|
@@ -1202,7 +1349,7 @@ export class EntityManager {
|
|
|
1202
1349
|
*/
|
|
1203
1350
|
async lock(entity, lockMode, options = {}) {
|
|
1204
1351
|
options = Utils.isPlainObject(options) ? options : { lockVersion: options };
|
|
1205
|
-
this.getContext(false).prepareOptions(options);
|
|
1352
|
+
options = this.getContext(false).prepareOptions(options);
|
|
1206
1353
|
await this.getUnitOfWork().lock(entity, { lockMode, ...options });
|
|
1207
1354
|
}
|
|
1208
1355
|
/**
|
|
@@ -1210,7 +1357,7 @@ export class EntityManager {
|
|
|
1210
1357
|
*/
|
|
1211
1358
|
async insert(entityNameOrEntity, data, options = {}) {
|
|
1212
1359
|
const em = this.getContext(false);
|
|
1213
|
-
em.prepareOptions(options);
|
|
1360
|
+
options = em.prepareOptions(options);
|
|
1214
1361
|
let entityName;
|
|
1215
1362
|
if (data === undefined) {
|
|
1216
1363
|
entityName = entityNameOrEntity.constructor;
|
|
@@ -1229,15 +1376,12 @@ export class EntityManager {
|
|
|
1229
1376
|
const meta = helper(data).__meta;
|
|
1230
1377
|
const payload = em.#comparator.prepareEntity(data);
|
|
1231
1378
|
const cs = new ChangeSet(data, ChangeSetType.CREATE, payload, meta);
|
|
1232
|
-
await em.#unitOfWork.getChangeSetPersister().executeInserts([cs], {
|
|
1379
|
+
await em.withSessionContext(options.ctx ?? em.#transactionContext, ctx => em.#unitOfWork.getChangeSetPersister().executeInserts([cs], { ...options, ctx }));
|
|
1233
1380
|
return cs.getPrimaryKey();
|
|
1234
1381
|
}
|
|
1235
1382
|
data = QueryHelper.processObjectParams(data);
|
|
1236
1383
|
validateParams(data, 'insert data');
|
|
1237
|
-
const res = await em.driver.nativeInsert(entityName, data, {
|
|
1238
|
-
ctx: em.#transactionContext,
|
|
1239
|
-
...options,
|
|
1240
|
-
});
|
|
1384
|
+
const res = await em.withSessionContext(options.ctx ?? em.#transactionContext, ctx => em.driver.nativeInsert(entityName, data, { ...options, ctx }));
|
|
1241
1385
|
return res.insertId;
|
|
1242
1386
|
}
|
|
1243
1387
|
/**
|
|
@@ -1277,12 +1421,9 @@ export class EntityManager {
|
|
|
1277
1421
|
overrides = overridesOrOptions;
|
|
1278
1422
|
}
|
|
1279
1423
|
options ??= {};
|
|
1280
|
-
em.prepareOptions(options);
|
|
1424
|
+
options = em.prepareOptions(options);
|
|
1281
1425
|
const meta = em.metadata.get(entityName);
|
|
1282
|
-
const res = await em.driver.nativeClone(entityName, where, overrides, {
|
|
1283
|
-
ctx: em.#transactionContext,
|
|
1284
|
-
...options,
|
|
1285
|
-
});
|
|
1426
|
+
const res = await em.withSessionContext(options.ctx ?? em.#transactionContext, ctx => em.driver.nativeClone(entityName, where, overrides, { ...options, ctx }));
|
|
1286
1427
|
const pk = res.insertId ?? res.row?.[meta.primaryKeys[0]];
|
|
1287
1428
|
return em.findOneOrFail(entityName, pk, {
|
|
1288
1429
|
schema: options.schema,
|
|
@@ -1293,7 +1434,7 @@ export class EntityManager {
|
|
|
1293
1434
|
*/
|
|
1294
1435
|
async insertMany(entityNameOrEntities, data, options = {}) {
|
|
1295
1436
|
const em = this.getContext(false);
|
|
1296
|
-
em.prepareOptions(options);
|
|
1437
|
+
options = em.prepareOptions(options);
|
|
1297
1438
|
let entityName;
|
|
1298
1439
|
if (data === undefined) {
|
|
1299
1440
|
entityName = entityNameOrEntities[0].constructor;
|
|
@@ -1317,15 +1458,12 @@ export class EntityManager {
|
|
|
1317
1458
|
const payload = em.#comparator.prepareEntity(row);
|
|
1318
1459
|
return new ChangeSet(row, ChangeSetType.CREATE, payload, meta);
|
|
1319
1460
|
});
|
|
1320
|
-
await em.#unitOfWork.getChangeSetPersister().executeInserts(css, {
|
|
1461
|
+
await em.withSessionContext(options.ctx ?? em.#transactionContext, ctx => em.#unitOfWork.getChangeSetPersister().executeInserts(css, { ...options, ctx }));
|
|
1321
1462
|
return css.map(cs => cs.getPrimaryKey());
|
|
1322
1463
|
}
|
|
1323
1464
|
data = data.map(row => QueryHelper.processObjectParams(row));
|
|
1324
1465
|
data.forEach(row => validateParams(row, 'insert data'));
|
|
1325
|
-
const res = await em.driver.nativeInsertMany(entityName, data, {
|
|
1326
|
-
ctx: em.#transactionContext,
|
|
1327
|
-
...options,
|
|
1328
|
-
});
|
|
1466
|
+
const res = await em.withSessionContext(options.ctx ?? em.#transactionContext, ctx => em.driver.nativeInsertMany(entityName, data, { ...options, ctx }));
|
|
1329
1467
|
if (res.insertedIds) {
|
|
1330
1468
|
return res.insertedIds;
|
|
1331
1469
|
}
|
|
@@ -1336,17 +1474,17 @@ export class EntityManager {
|
|
|
1336
1474
|
*/
|
|
1337
1475
|
async nativeUpdate(entityName, where, data, options = {}) {
|
|
1338
1476
|
const em = this.getContext(false);
|
|
1339
|
-
em.prepareOptions(options);
|
|
1477
|
+
options = em.prepareOptions(options);
|
|
1340
1478
|
await em.processUnionWhere(entityName, options, 'update');
|
|
1341
1479
|
data = QueryHelper.processObjectParams(data);
|
|
1342
1480
|
where = await em.processWhere(entityName, where, { ...options, convertCustomTypes: false }, 'update');
|
|
1343
1481
|
validateParams(data, 'update data');
|
|
1344
1482
|
validateParams(where, 'update condition');
|
|
1345
|
-
const res = await em.driver.nativeUpdate(entityName, where, data, {
|
|
1346
|
-
ctx: em.#transactionContext,
|
|
1483
|
+
const res = await em.withSessionContext(options.ctx ?? em.#transactionContext, ctx => em.driver.nativeUpdate(entityName, where, data, {
|
|
1347
1484
|
em,
|
|
1348
1485
|
...options,
|
|
1349
|
-
|
|
1486
|
+
ctx,
|
|
1487
|
+
}));
|
|
1350
1488
|
return res.affectedRows;
|
|
1351
1489
|
}
|
|
1352
1490
|
/**
|
|
@@ -1378,29 +1516,37 @@ export class EntityManager {
|
|
|
1378
1516
|
throw new Error(`Routine '${routine.name}' is not registered in the 'routines' config option.`);
|
|
1379
1517
|
}
|
|
1380
1518
|
const conn = em.driver.getConnection('write');
|
|
1381
|
-
return conn.callRoutine(routine, args,
|
|
1519
|
+
return em.withSessionContext(em.#transactionContext, ctx => conn.callRoutine(routine, args, ctx));
|
|
1382
1520
|
}
|
|
1383
1521
|
/**
|
|
1384
1522
|
* Fires native delete query. Calling this has no side effects on the context (identity map).
|
|
1385
1523
|
*/
|
|
1386
1524
|
async nativeDelete(entityName, where, options = {}) {
|
|
1387
1525
|
const em = this.getContext(false);
|
|
1388
|
-
em.prepareOptions(options);
|
|
1526
|
+
options = em.prepareOptions(options);
|
|
1389
1527
|
await em.processUnionWhere(entityName, options, 'delete');
|
|
1390
1528
|
where = (await em.processWhere(entityName, where, options, 'delete'));
|
|
1391
1529
|
validateParams(where, 'delete condition');
|
|
1392
|
-
const res = await em.driver.nativeDelete(entityName, where, {
|
|
1393
|
-
ctx: em.#transactionContext,
|
|
1530
|
+
const res = await em.withSessionContext(options.ctx ?? em.#transactionContext, ctx => em.driver.nativeDelete(entityName, where, {
|
|
1394
1531
|
em,
|
|
1395
1532
|
...options,
|
|
1396
|
-
|
|
1533
|
+
ctx,
|
|
1534
|
+
}));
|
|
1397
1535
|
return res.affectedRows;
|
|
1398
1536
|
}
|
|
1399
1537
|
/**
|
|
1400
|
-
* Maps raw database result to an entity and merges it to this EntityManager.
|
|
1538
|
+
* Maps raw database result to an entity and merges it to this EntityManager by default.
|
|
1539
|
+
* Use `disableIdentityMap` to return an isolated entity without affecting the current context.
|
|
1401
1540
|
*/
|
|
1402
1541
|
map(entityName, result, options = {}) {
|
|
1403
|
-
|
|
1542
|
+
if (options.disableIdentityMap ?? this.config.get('disableIdentityMap')) {
|
|
1543
|
+
const em = this.getContext(false);
|
|
1544
|
+
const fork = em.fork({ keepTransactionContext: true });
|
|
1545
|
+
const ret = fork.map(entityName, result, { ...options, disableIdentityMap: false });
|
|
1546
|
+
fork.clear();
|
|
1547
|
+
return ret;
|
|
1548
|
+
}
|
|
1549
|
+
const { mapped, disableIdentityMap, ...rest } = options;
|
|
1404
1550
|
const meta = this.metadata.get(entityName);
|
|
1405
1551
|
const data = (mapped ? result : this.driver.mapResult(result, meta));
|
|
1406
1552
|
for (const k of Object.keys(data)) {
|
|
@@ -1428,6 +1574,7 @@ export class EntityManager {
|
|
|
1428
1574
|
return this.merge(entityName.constructor, entityName, data);
|
|
1429
1575
|
}
|
|
1430
1576
|
const em = options.disableContextResolution ? this : this.getContext();
|
|
1577
|
+
options = { ...options };
|
|
1431
1578
|
options.schema ??= em.#schema;
|
|
1432
1579
|
options.validate ??= true;
|
|
1433
1580
|
options.cascade ??= true;
|
|
@@ -1461,6 +1608,7 @@ export class EntityManager {
|
|
|
1461
1608
|
*/
|
|
1462
1609
|
create(entityName, data, options = {}) {
|
|
1463
1610
|
const em = this.getContext();
|
|
1611
|
+
options = { ...options };
|
|
1464
1612
|
options.schema ??= em.#schema;
|
|
1465
1613
|
const entity = em.#entityFactory.create(entityName, data, {
|
|
1466
1614
|
...options,
|
|
@@ -1484,6 +1632,7 @@ export class EntityManager {
|
|
|
1484
1632
|
* Gets a reference to the entity identified by the given type and identifier without actually loading it, if the entity is not yet loaded
|
|
1485
1633
|
*/
|
|
1486
1634
|
getReference(entityName, id, options = {}) {
|
|
1635
|
+
options = { ...options };
|
|
1487
1636
|
options.schema ??= this.schema;
|
|
1488
1637
|
options.convertCustomTypes ??= false;
|
|
1489
1638
|
const meta = this.metadata.get(entityName);
|
|
@@ -1504,13 +1653,10 @@ export class EntityManager {
|
|
|
1504
1653
|
*/
|
|
1505
1654
|
async count(entityName, where = {}, options = {}) {
|
|
1506
1655
|
const em = this.getContext(false);
|
|
1507
|
-
|
|
1508
|
-
options = { ...options };
|
|
1509
|
-
em.prepareOptions(options);
|
|
1656
|
+
options = em.prepareOptions(options);
|
|
1510
1657
|
await em.tryFlush(entityName, options);
|
|
1511
1658
|
where = await em.processWhere(entityName, where, options, 'read');
|
|
1512
1659
|
options.populate = (await em.preparePopulate(entityName, options));
|
|
1513
|
-
options = { ...options };
|
|
1514
1660
|
// save the original hint value so we know it was infer/all
|
|
1515
1661
|
const meta = em.metadata.find(entityName);
|
|
1516
1662
|
options._populateWhere = options.populateWhere ?? this.config.get('populateWhere');
|
|
@@ -1524,7 +1670,7 @@ export class EntityManager {
|
|
|
1524
1670
|
if (cached?.data !== undefined) {
|
|
1525
1671
|
return cached.data;
|
|
1526
1672
|
}
|
|
1527
|
-
const count = await em.driver.count(entityName, where, {
|
|
1673
|
+
const count = await em.withSessionContext(options.ctx ?? em.#transactionContext, ctx => em.driver.count(entityName, where, { em, ...options, ctx }));
|
|
1528
1674
|
await em.storeCache(options.cache, cached, () => +count);
|
|
1529
1675
|
return +count;
|
|
1530
1676
|
}
|
|
@@ -1639,8 +1785,9 @@ export class EntityManager {
|
|
|
1639
1785
|
}
|
|
1640
1786
|
// For TPT inheritance, check the entity's own properties, not just the root's
|
|
1641
1787
|
// For STI, meta.properties includes all properties anyway
|
|
1642
|
-
|
|
1643
|
-
|
|
1788
|
+
// use an own-property check so inherited `Object.prototype` members (e.g. `__proto__`, `constructor`) are not treated as populatable
|
|
1789
|
+
const ret = Object.hasOwn(meta.properties, p);
|
|
1790
|
+
if (ret && parts.length > 0) {
|
|
1644
1791
|
return this.canPopulate(meta.properties[p].targetMeta.class, parts.join('.'));
|
|
1645
1792
|
}
|
|
1646
1793
|
return ret;
|
|
@@ -1654,7 +1801,7 @@ export class EntityManager {
|
|
|
1654
1801
|
return entities;
|
|
1655
1802
|
}
|
|
1656
1803
|
const em = this.getContext();
|
|
1657
|
-
em.prepareOptions(options);
|
|
1804
|
+
options = em.prepareOptions(options);
|
|
1658
1805
|
const entityName = arr[0].constructor;
|
|
1659
1806
|
const preparedPopulate = await em.preparePopulate(entityName, { populate: populate, filters: options.filters, populateHints: options.populateHints }, options.validate);
|
|
1660
1807
|
await em.#entityLoader.populate(entityName, arr, preparedPopulate, options);
|
|
@@ -1665,6 +1812,7 @@ export class EntityManager {
|
|
|
1665
1812
|
*/
|
|
1666
1813
|
fork(options = {}) {
|
|
1667
1814
|
const em = options.disableContextResolution ? this : this.getContext(false);
|
|
1815
|
+
options = { ...options };
|
|
1668
1816
|
options.clear ??= true;
|
|
1669
1817
|
options.useContext ??= false;
|
|
1670
1818
|
options.freshEventManager ??= false;
|
|
@@ -1687,6 +1835,18 @@ export class EntityManager {
|
|
|
1687
1835
|
}
|
|
1688
1836
|
fork.#filters = { ...em.#filters };
|
|
1689
1837
|
fork.#filterParams = Utils.copy(em.#filterParams);
|
|
1838
|
+
if (options.session) {
|
|
1839
|
+
// the fork's session replaces the parent context, but the copied `rls` filter params must stay consistent with
|
|
1840
|
+
// it — re-stage their variables underneath the explicit `session.variables`, which win on any conflict
|
|
1841
|
+
const variables = {};
|
|
1842
|
+
for (const name of Object.keys(fork.#filterParams)) {
|
|
1843
|
+
Object.assign(variables, computeRlsFilterVariables(findRlsFilterDefs(fork.metadata, name), fork.#filterParams[name]));
|
|
1844
|
+
}
|
|
1845
|
+
fork.mergeSessionContext({ ...options.session, variables: { ...variables, ...options.session.variables } });
|
|
1846
|
+
}
|
|
1847
|
+
else if (em.#sessionContext) {
|
|
1848
|
+
fork.#sessionContext = Utils.copy(em.#sessionContext);
|
|
1849
|
+
}
|
|
1690
1850
|
fork.loggerContext = Utils.merge({}, em.loggerContext, options.loggerContext);
|
|
1691
1851
|
fork.#schema = options.schema ?? em.#schema;
|
|
1692
1852
|
fork.signal = options.signal ?? em.signal;
|
|
@@ -2073,10 +2233,13 @@ export class EntityManager {
|
|
|
2073
2233
|
if (!Utils.isEmpty(options.fields) && !Utils.isEmpty(options.exclude)) {
|
|
2074
2234
|
throw new ValidationError(`Cannot combine 'fields' and 'exclude' option.`);
|
|
2075
2235
|
}
|
|
2076
|
-
options
|
|
2077
|
-
options
|
|
2078
|
-
|
|
2079
|
-
|
|
2236
|
+
// the options object belongs to the caller, everything below works on our own copy
|
|
2237
|
+
const opts = { ...options };
|
|
2238
|
+
opts.schema ??= this.#schema;
|
|
2239
|
+
opts.signal ??= this.signal;
|
|
2240
|
+
opts.inflightQueryAbortStrategy ??= this.inflightQueryAbortStrategy;
|
|
2241
|
+
opts.logging = opts.loggerContext = Utils.merge({ id: this.id }, this.loggerContext, opts.loggerContext, opts.logging);
|
|
2242
|
+
return opts;
|
|
2080
2243
|
}
|
|
2081
2244
|
/**
|
|
2082
2245
|
* @internal
|
|
@@ -2095,7 +2258,19 @@ export class EntityManager {
|
|
|
2095
2258
|
]) {
|
|
2096
2259
|
delete opts[k];
|
|
2097
2260
|
}
|
|
2098
|
-
|
|
2261
|
+
// the table name (plus discriminator value for STI) is stable across builds and processes,
|
|
2262
|
+
// unlike class names, which minifiers can mangle to the same short name for two entities
|
|
2263
|
+
const meta = this.metadata.find(entityName);
|
|
2264
|
+
const entityKey = meta?.tableName
|
|
2265
|
+
? [meta.schema, meta.tableName, meta.discriminatorValue]
|
|
2266
|
+
: Utils.className(entityName);
|
|
2267
|
+
const key = [entityKey, method, opts, where];
|
|
2268
|
+
// session context (row level security) scopes cached rows per tenant/role, avoiding cross-context serves
|
|
2269
|
+
const sessionContext = this.getContext(false).#sessionContext;
|
|
2270
|
+
if (sessionContext) {
|
|
2271
|
+
key.push(sessionContext);
|
|
2272
|
+
}
|
|
2273
|
+
return key;
|
|
2099
2274
|
}
|
|
2100
2275
|
/**
|
|
2101
2276
|
* @internal
|
|
@@ -2106,7 +2281,13 @@ export class EntityManager {
|
|
|
2106
2281
|
return undefined;
|
|
2107
2282
|
}
|
|
2108
2283
|
const em = this.getContext();
|
|
2109
|
-
|
|
2284
|
+
// a named cache key (`config[0]`) discards the computed `key`, which already carries the session context — so
|
|
2285
|
+
// scope it here too, otherwise a fork's rows would be served to another session context under the same name
|
|
2286
|
+
const cacheKey = Array.isArray(config)
|
|
2287
|
+
? em.#sessionContext
|
|
2288
|
+
? `${config[0]}|${JSON.stringify(em.#sessionContext)}`
|
|
2289
|
+
: config[0]
|
|
2290
|
+
: JSON.stringify(key);
|
|
2110
2291
|
const cached = await em.#resultCache.get(cacheKey);
|
|
2111
2292
|
if (!cached) {
|
|
2112
2293
|
return { key: cacheKey, data: cached };
|
|
@@ -2154,7 +2335,12 @@ export class EntityManager {
|
|
|
2154
2335
|
* ```
|
|
2155
2336
|
*/
|
|
2156
2337
|
async clearCache(cacheKey) {
|
|
2157
|
-
|
|
2338
|
+
const em = this.getContext();
|
|
2339
|
+
await em.#resultCache.remove(cacheKey);
|
|
2340
|
+
// named keys are scoped by the session context (see `tryCache`), so clear this context's variant too
|
|
2341
|
+
if (em.#sessionContext) {
|
|
2342
|
+
await em.#resultCache.remove(`${cacheKey}|${JSON.stringify(em.#sessionContext)}`);
|
|
2343
|
+
}
|
|
2158
2344
|
}
|
|
2159
2345
|
/**
|
|
2160
2346
|
* Returns the default schema of this EntityManager. Respects the context, so global EM will give you the contextual schema
|