@mikro-orm/core 7.0.0-rc.1 → 7.0.0-rc.3
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 +3 -2
- package/EntityManager.js +107 -43
- package/MikroORM.js +4 -4
- package/cache/FileCacheAdapter.js +1 -3
- package/connections/Connection.js +16 -3
- package/drivers/DatabaseDriver.js +26 -8
- package/drivers/IDatabaseDriver.d.ts +49 -1
- package/entity/BaseEntity.d.ts +3 -3
- package/entity/Collection.js +43 -17
- package/entity/EntityAssigner.js +23 -11
- package/entity/EntityFactory.js +36 -13
- package/entity/EntityHelper.js +27 -18
- package/entity/EntityLoader.d.ts +6 -6
- package/entity/EntityLoader.js +55 -22
- package/entity/EntityRepository.d.ts +1 -1
- package/entity/Reference.d.ts +1 -1
- package/entity/Reference.js +37 -8
- package/entity/WrappedEntity.d.ts +3 -3
- package/entity/WrappedEntity.js +5 -1
- package/entity/defineEntity.d.ts +202 -58
- package/entity/defineEntity.js +20 -24
- package/entity/utils.js +28 -26
- package/entity/validators.js +2 -1
- package/enums.js +12 -17
- package/errors.js +18 -8
- package/events/EventManager.js +1 -1
- package/exceptions.js +7 -2
- package/hydration/ObjectHydrator.js +27 -13
- package/index.d.ts +2 -2
- package/index.js +1 -1
- package/logging/DefaultLogger.js +3 -5
- package/logging/colors.js +3 -6
- package/metadata/EntitySchema.d.ts +2 -2
- package/metadata/EntitySchema.js +12 -2
- package/metadata/MetadataDiscovery.js +107 -48
- package/metadata/MetadataProvider.js +26 -1
- package/metadata/MetadataStorage.js +2 -4
- package/metadata/MetadataValidator.js +20 -5
- package/metadata/types.d.ts +2 -2
- package/naming-strategy/AbstractNamingStrategy.js +5 -2
- package/not-supported.js +5 -1
- package/package.json +40 -37
- package/platforms/Platform.d.ts +4 -1
- package/platforms/Platform.js +50 -24
- package/serialization/EntitySerializer.d.ts +3 -3
- package/serialization/EntitySerializer.js +7 -3
- package/serialization/EntityTransformer.js +6 -4
- package/serialization/SerializationContext.js +1 -1
- package/typings.d.ts +73 -33
- package/typings.js +11 -9
- package/unit-of-work/ChangeSet.js +4 -4
- package/unit-of-work/ChangeSetComputer.js +8 -6
- package/unit-of-work/ChangeSetPersister.js +15 -10
- package/unit-of-work/CommitOrderCalculator.js +4 -2
- package/unit-of-work/UnitOfWork.d.ts +7 -1
- package/unit-of-work/UnitOfWork.js +51 -22
- package/utils/AbstractMigrator.d.ts +101 -0
- package/utils/AbstractMigrator.js +303 -0
- package/utils/AbstractSchemaGenerator.js +2 -1
- package/utils/AsyncContext.js +1 -1
- package/utils/Configuration.d.ts +3 -1
- package/utils/Configuration.js +8 -4
- package/utils/Cursor.js +4 -2
- package/utils/DataloaderUtils.js +15 -12
- package/utils/EntityComparator.js +51 -43
- package/utils/QueryHelper.js +38 -26
- package/utils/RawQueryFragment.js +3 -2
- package/utils/TransactionManager.js +2 -1
- package/utils/Utils.d.ts +1 -1
- package/utils/Utils.js +36 -30
- package/utils/env-vars.js +6 -5
- package/utils/fs-utils.d.ts +1 -0
- package/utils/fs-utils.js +6 -5
- package/utils/index.d.ts +0 -2
- package/utils/index.js +0 -2
- package/utils/upsert-utils.js +6 -3
package/EntityManager.d.ts
CHANGED
|
@@ -121,6 +121,7 @@ export declare class EntityManager<Driver extends IDatabaseDriver = IDatabaseDri
|
|
|
121
121
|
}): T;
|
|
122
122
|
setFlushMode(flushMode?: FlushMode | `${FlushMode}`): void;
|
|
123
123
|
protected processWhere<Entity extends object, Hint extends string = never, Fields extends string = '*', Excludes extends string = never>(entityName: EntityName<Entity>, where: FilterQuery<Entity>, options: FindOptions<Entity, Hint, Fields, Excludes> | FindOneOptions<Entity, Hint, Fields, Excludes>, type: 'read' | 'update' | 'delete'): Promise<FilterQuery<Entity>>;
|
|
124
|
+
protected processUnionWhere<Entity extends object, Hint extends string = never>(entityName: EntityName<Entity>, options: FindOptions<Entity, Hint, any, any> | CountOptions<Entity, Hint> | UpdateOptions<Entity> | DeleteOptions<Entity>, type: 'read' | 'update' | 'delete'): Promise<void>;
|
|
124
125
|
protected applyDiscriminatorCondition<Entity extends object>(entityName: EntityName<Entity>, where: FilterQuery<Entity>): FilterQuery<Entity>;
|
|
125
126
|
protected createPopulateWhere<Entity extends object>(cond: ObjectQuery<Entity>, options: FindOptions<Entity, any, any, any> | FindOneOptions<Entity, any, any, any> | CountOptions<Entity, any>): ObjectQuery<Entity>;
|
|
126
127
|
protected getJoinedFilters<Entity extends object>(meta: EntityMetadata<Entity>, options: FindOptions<Entity, any, any, any> | FindOneOptions<Entity, any, any, any>): Promise<ObjectQuery<Entity> | undefined>;
|
|
@@ -456,7 +457,7 @@ export declare class EntityManager<Driver extends IDatabaseDriver = IDatabaseDri
|
|
|
456
457
|
/**
|
|
457
458
|
* Loads specified relations in batch. This will execute one query for each relation, that will populate it on all the specified entities.
|
|
458
459
|
*/
|
|
459
|
-
populate<Entity extends object, Naked extends FromEntityType<UnboxArray<Entity>> = FromEntityType<UnboxArray<Entity>>, Hint extends string = never, Fields extends string =
|
|
460
|
+
populate<Entity extends object, Naked extends FromEntityType<UnboxArray<Entity>> = FromEntityType<UnboxArray<Entity>>, Hint extends string = never, Fields extends string = never, Excludes extends string = never>(entities: Entity, populate: readonly AutoPath<Naked, Hint, PopulatePath.ALL>[] | false, options?: EntityLoaderOptions<Naked, Fields, Excludes>): Promise<Entity extends object[] ? MergeLoaded<ArrayElement<Entity>, Naked, Hint, Fields, Excludes>[] : MergeLoaded<Entity, Naked, Hint, Fields, Excludes>>;
|
|
460
461
|
/**
|
|
461
462
|
* Returns new EntityManager instance with its own identity map
|
|
462
463
|
*/
|
|
@@ -593,7 +594,7 @@ export interface MergeOptions {
|
|
|
593
594
|
schema?: string;
|
|
594
595
|
disableContextResolution?: boolean;
|
|
595
596
|
validate?: boolean;
|
|
596
|
-
cascade?: boolean
|
|
597
|
+
cascade?: boolean /** @default true */;
|
|
597
598
|
}
|
|
598
599
|
export interface ForkOptions {
|
|
599
600
|
/** do we want a clear identity map? defaults to true */
|
package/EntityManager.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { getOnConflictReturningFields, getWhereCondition } from './utils/upsert-utils.js';
|
|
2
2
|
import { Utils } from './utils/Utils.js';
|
|
3
3
|
import { Cursor } from './utils/Cursor.js';
|
|
4
|
-
import { DataloaderUtils } from './utils/DataloaderUtils.js';
|
|
5
4
|
import { QueryHelper } from './utils/QueryHelper.js';
|
|
6
5
|
import { TransactionContext } from './utils/TransactionContext.js';
|
|
7
6
|
import { isRaw, Raw } from './utils/RawQueryFragment.js';
|
|
@@ -124,7 +123,7 @@ export class EntityManager {
|
|
|
124
123
|
else {
|
|
125
124
|
options.orderBy ??= {};
|
|
126
125
|
}
|
|
127
|
-
options.populate = await em.preparePopulate(entityName, options);
|
|
126
|
+
options.populate = (await em.preparePopulate(entityName, options));
|
|
128
127
|
const populate = options.populate;
|
|
129
128
|
const cacheKey = em.cacheKey(entityName, options, 'em.find', where);
|
|
130
129
|
const cached = await em.tryCache(entityName, options.cache, cacheKey, options.refresh, true);
|
|
@@ -142,6 +141,7 @@ export class EntityManager {
|
|
|
142
141
|
options._populateWhere = options.populateWhere ?? this.config.get('populateWhere');
|
|
143
142
|
options.populateWhere = this.createPopulateWhere({ ...where }, options);
|
|
144
143
|
options.populateFilter = await this.getJoinedFilters(meta, options);
|
|
144
|
+
await em.processUnionWhere(entityName, options, 'read');
|
|
145
145
|
const results = await em.driver.find(entityName, where, { ctx: em.transactionContext, em, ...options });
|
|
146
146
|
if (results.length === 0) {
|
|
147
147
|
await em.storeCache(options.cache, cached, []);
|
|
@@ -195,10 +195,10 @@ export class EntityManager {
|
|
|
195
195
|
em.prepareOptions(options);
|
|
196
196
|
options.strategy = 'joined';
|
|
197
197
|
await em.tryFlush(entityName, options);
|
|
198
|
-
const where = await em.processWhere(entityName, options.where ?? {}, options, 'read');
|
|
198
|
+
const where = (await em.processWhere(entityName, options.where ?? {}, options, 'read'));
|
|
199
199
|
validateParams(where);
|
|
200
200
|
options.orderBy = options.orderBy || {};
|
|
201
|
-
options.populate = await em.preparePopulate(entityName, options);
|
|
201
|
+
options.populate = (await em.preparePopulate(entityName, options));
|
|
202
202
|
const meta = this.metadata.get(entityName);
|
|
203
203
|
options = { ...options };
|
|
204
204
|
// save the original hint value so we know it was infer/all
|
|
@@ -299,6 +299,14 @@ export class EntityManager {
|
|
|
299
299
|
where = this.applyDiscriminatorCondition(entityName, where);
|
|
300
300
|
return where;
|
|
301
301
|
}
|
|
302
|
+
async processUnionWhere(entityName, options, type) {
|
|
303
|
+
if (options.unionWhere?.length) {
|
|
304
|
+
if (!this.driver.getPlatform().supportsUnionWhere()) {
|
|
305
|
+
throw new Error(`unionWhere is only supported on SQL drivers`);
|
|
306
|
+
}
|
|
307
|
+
options.unionWhere = (await Promise.all(options.unionWhere.map(branch => this.processWhere(entityName, branch, options, type))));
|
|
308
|
+
}
|
|
309
|
+
}
|
|
302
310
|
// this method only handles the problem for mongo driver, SQL drivers have their implementation inside QueryBuilder
|
|
303
311
|
applyDiscriminatorCondition(entityName, where) {
|
|
304
312
|
const meta = this.metadata.find(entityName);
|
|
@@ -315,7 +323,10 @@ export class EntityManager {
|
|
|
315
323
|
};
|
|
316
324
|
lookUpChildren(children, meta.class);
|
|
317
325
|
/* v8 ignore next */
|
|
318
|
-
where[meta.root.discriminatorColumn] =
|
|
326
|
+
where[meta.root.discriminatorColumn] =
|
|
327
|
+
children.length > 0
|
|
328
|
+
? { $in: [meta.discriminatorValue, ...children.map(c => c.discriminatorValue)] }
|
|
329
|
+
: meta.discriminatorValue;
|
|
319
330
|
return where;
|
|
320
331
|
}
|
|
321
332
|
createPopulateWhere(cond, options) {
|
|
@@ -382,10 +393,11 @@ export class EntityManager {
|
|
|
382
393
|
}
|
|
383
394
|
const ret = options.populate;
|
|
384
395
|
for (const prop of meta.relations) {
|
|
385
|
-
if (prop.object
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
396
|
+
if (prop.object ||
|
|
397
|
+
![ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind) ||
|
|
398
|
+
!((options.fields?.length ?? 0) === 0 ||
|
|
399
|
+
options.fields?.some(f => prop.name === f || prop.name.startsWith(`${String(f)}.`))) ||
|
|
400
|
+
(parent?.class === prop.targetMeta.root.class && parent.propName === prop.inversedBy)) {
|
|
389
401
|
continue;
|
|
390
402
|
}
|
|
391
403
|
options = { ...options, filters: QueryHelper.mergePropertyFilters(prop.filters, options.filters) };
|
|
@@ -425,9 +437,7 @@ export class EntityManager {
|
|
|
425
437
|
const ret = [];
|
|
426
438
|
const active = new Set();
|
|
427
439
|
const push = (source) => {
|
|
428
|
-
const activeFilters = QueryHelper
|
|
429
|
-
.getActiveFilters(meta, options, source)
|
|
430
|
-
.filter(f => !active.has(f.name));
|
|
440
|
+
const activeFilters = QueryHelper.getActiveFilters(meta, options, source).filter(f => !active.has(f.name));
|
|
431
441
|
filters.push(...activeFilters);
|
|
432
442
|
activeFilters.forEach(f => active.add(f.name));
|
|
433
443
|
};
|
|
@@ -441,6 +451,7 @@ export class EntityManager {
|
|
|
441
451
|
let cond;
|
|
442
452
|
if (filter.cond instanceof Function) {
|
|
443
453
|
// @ts-ignore
|
|
454
|
+
// oxfmt-ignore
|
|
444
455
|
const args = Utils.isPlainObject(options?.[filter.name]) ? options[filter.name] : this.getContext().filterParams[filter.name];
|
|
445
456
|
if (!args && filter.cond.length > 0 && filter.args !== false) {
|
|
446
457
|
throw new Error(`No arguments provided for filter '${filter.name}'`);
|
|
@@ -583,12 +594,18 @@ export class EntityManager {
|
|
|
583
594
|
for (const e of fork.unitOfWork.getIdentityMap()) {
|
|
584
595
|
const ref = em.getReference(e.constructor, helper(e).getPrimaryKey());
|
|
585
596
|
const data = helper(e).serialize({ ignoreSerializers: true, includeHidden: true, convertCustomTypes: false });
|
|
586
|
-
em.config
|
|
597
|
+
em.config
|
|
598
|
+
.getHydrator(this.metadata)
|
|
599
|
+
.hydrate(ref, helper(ref).__meta, data, em.entityFactory, 'full', false, false);
|
|
587
600
|
Utils.merge(helper(ref).__originalEntityData, this.comparator.prepareEntity(e));
|
|
588
601
|
found ||= ref === entity;
|
|
589
602
|
}
|
|
590
603
|
if (!found) {
|
|
591
|
-
const data = helper(reloaded).serialize({
|
|
604
|
+
const data = helper(reloaded).serialize({
|
|
605
|
+
ignoreSerializers: true,
|
|
606
|
+
includeHidden: true,
|
|
607
|
+
convertCustomTypes: true,
|
|
608
|
+
});
|
|
592
609
|
em.config.getHydrator(this.metadata).hydrate(entity, wrapped.__meta, data, em.entityFactory, 'full', false, true);
|
|
593
610
|
Utils.merge(wrapped.__originalEntityData, this.comparator.prepareEntity(reloaded));
|
|
594
611
|
}
|
|
@@ -624,7 +641,7 @@ export class EntityManager {
|
|
|
624
641
|
return em.lockAndPopulate(meta, entity, where, options);
|
|
625
642
|
}
|
|
626
643
|
validateParams(where);
|
|
627
|
-
options.populate = await em.preparePopulate(entityName, options);
|
|
644
|
+
options.populate = (await em.preparePopulate(entityName, options));
|
|
628
645
|
const cacheKey = em.cacheKey(entityName, options, 'em.findOne', where);
|
|
629
646
|
const cached = await em.tryCache(entityName, options.cache, cacheKey, options.refresh, true);
|
|
630
647
|
if (cached?.data !== undefined) {
|
|
@@ -643,6 +660,7 @@ export class EntityManager {
|
|
|
643
660
|
options._populateWhere = options.populateWhere ?? this.config.get('populateWhere');
|
|
644
661
|
options.populateWhere = this.createPopulateWhere({ ...where }, options);
|
|
645
662
|
options.populateFilter = await this.getJoinedFilters(meta, options);
|
|
663
|
+
await em.processUnionWhere(entityName, options, 'read');
|
|
646
664
|
const data = await em.driver.findOne(entityName, where, {
|
|
647
665
|
ctx: em.transactionContext,
|
|
648
666
|
em,
|
|
@@ -771,9 +789,12 @@ export class EntityManager {
|
|
|
771
789
|
initialized: true,
|
|
772
790
|
schema: options.schema,
|
|
773
791
|
});
|
|
774
|
-
const uniqueFields = options.onConflictFields ??
|
|
792
|
+
const uniqueFields = options.onConflictFields ??
|
|
793
|
+
(Utils.isPlainObject(where) ? Object.keys(where) : meta.primaryKeys);
|
|
775
794
|
const returning = getOnConflictReturningFields(meta, data, uniqueFields, options);
|
|
776
|
-
if (options.onConflictAction === 'ignore' ||
|
|
795
|
+
if (options.onConflictAction === 'ignore' ||
|
|
796
|
+
!helper(entity).hasPrimaryKey() ||
|
|
797
|
+
(returning.length > 0 && !(this.getPlatform().usesReturningStatement() && ret.row))) {
|
|
777
798
|
const where = {};
|
|
778
799
|
if (Array.isArray(uniqueFields)) {
|
|
779
800
|
for (const prop of uniqueFields) {
|
|
@@ -897,7 +918,9 @@ export class EntityManager {
|
|
|
897
918
|
}
|
|
898
919
|
}
|
|
899
920
|
const unique = options.onConflictFields ?? meta.props.filter(p => p.unique).map(p => p.name);
|
|
900
|
-
propIndex =
|
|
921
|
+
propIndex =
|
|
922
|
+
!isRaw(unique) &&
|
|
923
|
+
unique.findIndex(p => data[p] ?? data[p.substring(0, p.indexOf('.'))] != null);
|
|
901
924
|
const tmp = getWhereCondition(meta, options.onConflictFields, row, where);
|
|
902
925
|
propIndex = tmp.propIndex;
|
|
903
926
|
where = QueryHelper.processWhere({
|
|
@@ -930,12 +953,16 @@ export class EntityManager {
|
|
|
930
953
|
entitiesByData.clear();
|
|
931
954
|
const loadPK = new Map();
|
|
932
955
|
allData.forEach((row, i) => {
|
|
933
|
-
em.unitOfWork
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
956
|
+
em.unitOfWork
|
|
957
|
+
.getChangeSetPersister()
|
|
958
|
+
.mapReturnedValues(Utils.isEntity(data[i]) ? data[i] : null, Utils.isEntity(data[i]) ? {} : data[i], res.rows?.[i], meta, true);
|
|
959
|
+
const entity = Utils.isEntity(data[i])
|
|
960
|
+
? data[i]
|
|
961
|
+
: em.entityFactory.create(entityName, row, {
|
|
962
|
+
refresh: true,
|
|
963
|
+
initialized: true,
|
|
964
|
+
schema: options.schema,
|
|
965
|
+
});
|
|
939
966
|
if (!helper(entity).hasPrimaryKey()) {
|
|
940
967
|
loadPK.set(entity, allWhere[i]);
|
|
941
968
|
}
|
|
@@ -943,12 +970,15 @@ export class EntityManager {
|
|
|
943
970
|
entitiesByData.set(row, entity);
|
|
944
971
|
});
|
|
945
972
|
// skip if we got the PKs via returning statement (`rows`)
|
|
973
|
+
// oxfmt-ignore
|
|
946
974
|
const uniqueFields = options.onConflictFields ?? (Utils.isPlainObject(allWhere[0]) ? Object.keys(allWhere[0]).flatMap(key => Utils.splitPrimaryKeys(key)) : meta.primaryKeys);
|
|
947
975
|
const returning = getOnConflictReturningFields(meta, data[0], uniqueFields, options);
|
|
948
976
|
const reloadFields = returning.length > 0 && !(this.getPlatform().usesReturningStatement() && res.rows?.length);
|
|
949
977
|
if (options.onConflictAction === 'ignore' || (!res.rows?.length && loadPK.size > 0) || reloadFields) {
|
|
950
978
|
const unique = meta.hydrateProps.filter(p => !p.lazy).map(p => p.name);
|
|
951
|
-
const add = new Set(propIndex !== false && propIndex >= 0
|
|
979
|
+
const add = new Set(propIndex !== false && propIndex >= 0
|
|
980
|
+
? [unique[propIndex]]
|
|
981
|
+
: []);
|
|
952
982
|
for (const cond of loadPK.values()) {
|
|
953
983
|
Utils.keys(cond).forEach(key => add.add(key));
|
|
954
984
|
}
|
|
@@ -961,7 +991,9 @@ export class EntityManager {
|
|
|
961
991
|
});
|
|
962
992
|
});
|
|
963
993
|
const data2 = await this.driver.find(meta.class, where, {
|
|
964
|
-
fields: returning
|
|
994
|
+
fields: returning
|
|
995
|
+
.concat(...add)
|
|
996
|
+
.concat(...(Array.isArray(uniqueFields) ? uniqueFields : [])),
|
|
965
997
|
ctx: em.transactionContext,
|
|
966
998
|
convertCustomTypes: true,
|
|
967
999
|
connectionType: 'write',
|
|
@@ -1137,7 +1169,10 @@ export class EntityManager {
|
|
|
1137
1169
|
}
|
|
1138
1170
|
data = QueryHelper.processObjectParams(data);
|
|
1139
1171
|
validateParams(data, 'insert data');
|
|
1140
|
-
const res = await em.driver.nativeInsert(entityName, data, {
|
|
1172
|
+
const res = await em.driver.nativeInsert(entityName, data, {
|
|
1173
|
+
ctx: em.transactionContext,
|
|
1174
|
+
...options,
|
|
1175
|
+
});
|
|
1141
1176
|
return res.insertId;
|
|
1142
1177
|
}
|
|
1143
1178
|
/**
|
|
@@ -1177,7 +1212,10 @@ export class EntityManager {
|
|
|
1177
1212
|
}
|
|
1178
1213
|
data = data.map(row => QueryHelper.processObjectParams(row));
|
|
1179
1214
|
data.forEach(row => validateParams(row, 'insert data'));
|
|
1180
|
-
const res = await em.driver.nativeInsertMany(entityName, data, {
|
|
1215
|
+
const res = await em.driver.nativeInsertMany(entityName, data, {
|
|
1216
|
+
ctx: em.transactionContext,
|
|
1217
|
+
...options,
|
|
1218
|
+
});
|
|
1181
1219
|
if (res.insertedIds) {
|
|
1182
1220
|
return res.insertedIds;
|
|
1183
1221
|
}
|
|
@@ -1189,11 +1227,16 @@ export class EntityManager {
|
|
|
1189
1227
|
async nativeUpdate(entityName, where, data, options = {}) {
|
|
1190
1228
|
const em = this.getContext(false);
|
|
1191
1229
|
em.prepareOptions(options);
|
|
1230
|
+
await em.processUnionWhere(entityName, options, 'update');
|
|
1192
1231
|
data = QueryHelper.processObjectParams(data);
|
|
1193
1232
|
where = await em.processWhere(entityName, where, { ...options, convertCustomTypes: false }, 'update');
|
|
1194
1233
|
validateParams(data, 'update data');
|
|
1195
1234
|
validateParams(where, 'update condition');
|
|
1196
|
-
const res = await em.driver.nativeUpdate(entityName, where, data, {
|
|
1235
|
+
const res = await em.driver.nativeUpdate(entityName, where, data, {
|
|
1236
|
+
ctx: em.transactionContext,
|
|
1237
|
+
em,
|
|
1238
|
+
...options,
|
|
1239
|
+
});
|
|
1197
1240
|
return res.affectedRows;
|
|
1198
1241
|
}
|
|
1199
1242
|
/**
|
|
@@ -1202,9 +1245,14 @@ export class EntityManager {
|
|
|
1202
1245
|
async nativeDelete(entityName, where, options = {}) {
|
|
1203
1246
|
const em = this.getContext(false);
|
|
1204
1247
|
em.prepareOptions(options);
|
|
1205
|
-
|
|
1248
|
+
await em.processUnionWhere(entityName, options, 'delete');
|
|
1249
|
+
where = (await em.processWhere(entityName, where, options, 'delete'));
|
|
1206
1250
|
validateParams(where, 'delete condition');
|
|
1207
|
-
const res = await em.driver.nativeDelete(entityName, where, {
|
|
1251
|
+
const res = await em.driver.nativeDelete(entityName, where, {
|
|
1252
|
+
ctx: em.transactionContext,
|
|
1253
|
+
em,
|
|
1254
|
+
...options,
|
|
1255
|
+
});
|
|
1208
1256
|
return res.affectedRows;
|
|
1209
1257
|
}
|
|
1210
1258
|
/**
|
|
@@ -1215,7 +1263,10 @@ export class EntityManager {
|
|
|
1215
1263
|
const data = this.driver.mapResult(result, meta);
|
|
1216
1264
|
for (const k of Object.keys(data)) {
|
|
1217
1265
|
const prop = meta.properties[k];
|
|
1218
|
-
if (prop?.kind === ReferenceKind.SCALAR &&
|
|
1266
|
+
if (prop?.kind === ReferenceKind.SCALAR &&
|
|
1267
|
+
SCALAR_TYPES.has(prop.runtimeType) &&
|
|
1268
|
+
!prop.customType &&
|
|
1269
|
+
(prop.setter || !prop.getter)) {
|
|
1219
1270
|
validateProperty(prop, data[k], data);
|
|
1220
1271
|
}
|
|
1221
1272
|
}
|
|
@@ -1244,7 +1295,9 @@ export class EntityManager {
|
|
|
1244
1295
|
return entity;
|
|
1245
1296
|
}
|
|
1246
1297
|
const dataIsEntity = Utils.isEntity(data);
|
|
1247
|
-
entity = dataIsEntity
|
|
1298
|
+
entity = dataIsEntity
|
|
1299
|
+
? data
|
|
1300
|
+
: em.entityFactory.create(entityName, data, { merge: true, ...options });
|
|
1248
1301
|
const visited = options.cascade ? undefined : new Set([entity]);
|
|
1249
1302
|
em.unitOfWork.merge(entity, visited);
|
|
1250
1303
|
return entity;
|
|
@@ -1314,7 +1367,7 @@ export class EntityManager {
|
|
|
1314
1367
|
em.prepareOptions(options);
|
|
1315
1368
|
await em.tryFlush(entityName, options);
|
|
1316
1369
|
where = await em.processWhere(entityName, where, options, 'read');
|
|
1317
|
-
options.populate = await em.preparePopulate(entityName, options);
|
|
1370
|
+
options.populate = (await em.preparePopulate(entityName, options));
|
|
1318
1371
|
options = { ...options };
|
|
1319
1372
|
// save the original hint value so we know it was infer/all
|
|
1320
1373
|
const meta = em.metadata.find(entityName);
|
|
@@ -1323,6 +1376,7 @@ export class EntityManager {
|
|
|
1323
1376
|
options.populateFilter = await this.getJoinedFilters(meta, options);
|
|
1324
1377
|
validateParams(where);
|
|
1325
1378
|
delete options.orderBy;
|
|
1379
|
+
await em.processUnionWhere(entityName, options, 'read');
|
|
1326
1380
|
const cacheKey = em.cacheKey(entityName, options, 'em.count', where);
|
|
1327
1381
|
const cached = await em.tryCache(entityName, options.cache, cacheKey);
|
|
1328
1382
|
if (cached?.data !== undefined) {
|
|
@@ -1460,7 +1514,8 @@ export class EntityManager {
|
|
|
1460
1514
|
em.config.set('allowGlobalContext', true);
|
|
1461
1515
|
const fork = new em.constructor(em.config, em.driver, em.metadata, options.useContext, eventManager);
|
|
1462
1516
|
fork.setFlushMode(options.flushMode ?? em.flushMode);
|
|
1463
|
-
fork.disableTransactions =
|
|
1517
|
+
fork.disableTransactions =
|
|
1518
|
+
options.disableTransactions ?? this.disableTransactions ?? this.config.get('disableTransactions');
|
|
1464
1519
|
em.config.set('allowGlobalContext', allowGlobalContext);
|
|
1465
1520
|
if (options.keepTransactionContext) {
|
|
1466
1521
|
fork.transactionContext = em.transactionContext;
|
|
@@ -1627,11 +1682,14 @@ export class EntityManager {
|
|
|
1627
1682
|
const ret = [];
|
|
1628
1683
|
for (let field of fields) {
|
|
1629
1684
|
if (field === PopulatePath.ALL || field.startsWith(`${PopulatePath.ALL}.`)) {
|
|
1630
|
-
ret.push(...meta.props
|
|
1685
|
+
ret.push(...meta.props
|
|
1686
|
+
.filter(prop => prop.lazy || [ReferenceKind.SCALAR, ReferenceKind.EMBEDDED].includes(prop.kind))
|
|
1687
|
+
.map(prop => prop.name));
|
|
1631
1688
|
continue;
|
|
1632
1689
|
}
|
|
1633
1690
|
field = field.split(':')[0];
|
|
1634
|
-
if (!field.includes('.') &&
|
|
1691
|
+
if (!field.includes('.') &&
|
|
1692
|
+
![ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(meta.properties[field].kind)) {
|
|
1635
1693
|
ret.push(field);
|
|
1636
1694
|
continue;
|
|
1637
1695
|
}
|
|
@@ -1660,7 +1718,8 @@ export class EntityManager {
|
|
|
1660
1718
|
return populate;
|
|
1661
1719
|
}
|
|
1662
1720
|
if (typeof options.populate !== 'boolean') {
|
|
1663
|
-
options.populate = Utils.asArray(options.populate)
|
|
1721
|
+
options.populate = Utils.asArray(options.populate)
|
|
1722
|
+
.map(field => {
|
|
1664
1723
|
/* v8 ignore next */
|
|
1665
1724
|
if (typeof field === 'boolean' || field === PopulatePath.ALL) {
|
|
1666
1725
|
return [{ field: meta.primaryKeys[0], strategy: options.strategy, all: !!field }]; //
|
|
@@ -1675,7 +1734,8 @@ export class EntityManager {
|
|
|
1675
1734
|
return [{ field, strategy: options.strategy }];
|
|
1676
1735
|
}
|
|
1677
1736
|
return [field];
|
|
1678
|
-
})
|
|
1737
|
+
})
|
|
1738
|
+
.flat();
|
|
1679
1739
|
}
|
|
1680
1740
|
const populate = this.entityLoader.normalizePopulate(entityName, options.populate, options.strategy, true, options.exclude);
|
|
1681
1741
|
const invalid = populate.find(({ field }) => !this.canPopulate(entityName, field));
|
|
@@ -1777,7 +1837,7 @@ export class EntityManager {
|
|
|
1777
1837
|
config ??= this.config.get('resultCache').global;
|
|
1778
1838
|
if (config) {
|
|
1779
1839
|
const em = this.getContext();
|
|
1780
|
-
const expiration = Array.isArray(config) ? config[1] :
|
|
1840
|
+
const expiration = Array.isArray(config) ? config[1] : typeof config === 'number' ? config : undefined;
|
|
1781
1841
|
await em.resultCache.set(key.key, data instanceof Function ? data() : data, '', expiration);
|
|
1782
1842
|
}
|
|
1783
1843
|
}
|
|
@@ -1816,11 +1876,15 @@ export class EntityManager {
|
|
|
1816
1876
|
if (em.loaders[type]) {
|
|
1817
1877
|
return em.loaders[type];
|
|
1818
1878
|
}
|
|
1879
|
+
const { DataloaderUtils } = await import('@mikro-orm/core/dataloader');
|
|
1819
1880
|
const DataLoader = await DataloaderUtils.getDataLoader();
|
|
1820
1881
|
switch (type) {
|
|
1821
|
-
case 'ref':
|
|
1822
|
-
|
|
1823
|
-
case 'm
|
|
1882
|
+
case 'ref':
|
|
1883
|
+
return (em.loaders[type] ??= new DataLoader(DataloaderUtils.getRefBatchLoadFn(em)));
|
|
1884
|
+
case '1:m':
|
|
1885
|
+
return (em.loaders[type] ??= new DataLoader(DataloaderUtils.getColBatchLoadFn(em)));
|
|
1886
|
+
case 'm:n':
|
|
1887
|
+
return (em.loaders[type] ??= new DataLoader(DataloaderUtils.getManyToManyColBatchLoadFn(em)));
|
|
1824
1888
|
}
|
|
1825
1889
|
}
|
|
1826
1890
|
/**
|
package/MikroORM.js
CHANGED
|
@@ -104,9 +104,7 @@ export class MikroORM {
|
|
|
104
104
|
*/
|
|
105
105
|
constructor(options) {
|
|
106
106
|
const env = loadEnvironmentVars();
|
|
107
|
-
options = options.preferEnvVars
|
|
108
|
-
? Utils.merge(options, env)
|
|
109
|
-
: Utils.merge(env, options);
|
|
107
|
+
options = options.preferEnvVars ? Utils.merge(options, env) : Utils.merge(env, options);
|
|
110
108
|
this.config = new Configuration(options);
|
|
111
109
|
const discovery = this.config.get('discovery');
|
|
112
110
|
this.driver = this.config.getDriver();
|
|
@@ -213,6 +211,8 @@ export class MikroORM {
|
|
|
213
211
|
* Gets the EntityGenerator.
|
|
214
212
|
*/
|
|
215
213
|
get entityGenerator() {
|
|
216
|
-
return this.driver
|
|
214
|
+
return this.driver
|
|
215
|
+
.getPlatform()
|
|
216
|
+
.getExtension('EntityGenerator', '@mikro-orm/entity-generator', '@mikro-orm/entity-generator', this.em);
|
|
217
217
|
}
|
|
218
218
|
}
|
|
@@ -68,9 +68,7 @@ export class FileCacheAdapter {
|
|
|
68
68
|
if (!this.options.combined) {
|
|
69
69
|
return;
|
|
70
70
|
}
|
|
71
|
-
let path = typeof this.options.combined === 'string'
|
|
72
|
-
? this.options.combined
|
|
73
|
-
: './metadata.json';
|
|
71
|
+
let path = typeof this.options.combined === 'string' ? this.options.combined : './metadata.json';
|
|
74
72
|
path = fs.normalizePath(this.options.cacheDir, path);
|
|
75
73
|
this.options.combined = path; // override in the options, so we can log it from the CLI in `cache:generate` command
|
|
76
74
|
writeFileSync(path, JSON.stringify(this.cache, null, this.pretty ? 2 : undefined));
|
|
@@ -16,7 +16,18 @@ export class Connection {
|
|
|
16
16
|
this.options = Utils.copy(options);
|
|
17
17
|
}
|
|
18
18
|
else {
|
|
19
|
-
const props = [
|
|
19
|
+
const props = [
|
|
20
|
+
'dbName',
|
|
21
|
+
'clientUrl',
|
|
22
|
+
'host',
|
|
23
|
+
'port',
|
|
24
|
+
'user',
|
|
25
|
+
'password',
|
|
26
|
+
'multipleStatements',
|
|
27
|
+
'pool',
|
|
28
|
+
'schema',
|
|
29
|
+
'driverOptions',
|
|
30
|
+
];
|
|
20
31
|
this.options = props.reduce((o, i) => {
|
|
21
32
|
o[i] = this.config.get(i);
|
|
22
33
|
return o;
|
|
@@ -89,8 +100,10 @@ export class Connection {
|
|
|
89
100
|
this.options.host = ret.host = this.options.host ?? this.config.get('host', decodeURIComponent(url.hostname));
|
|
90
101
|
this.options.port = ret.port = this.options.port ?? this.config.get('port', +url.port);
|
|
91
102
|
this.options.user = ret.user = this.options.user ?? this.config.get('user', decodeURIComponent(url.username));
|
|
92
|
-
this.options.password = ret.password =
|
|
93
|
-
|
|
103
|
+
this.options.password = ret.password =
|
|
104
|
+
this.options.password ?? this.config.get('password', decodeURIComponent(url.password));
|
|
105
|
+
this.options.dbName = ret.database =
|
|
106
|
+
this.options.dbName ?? this.config.get('dbName', decodeURIComponent(url.pathname).replace(/^\//, ''));
|
|
94
107
|
}
|
|
95
108
|
return ret;
|
|
96
109
|
}
|
|
@@ -157,11 +157,11 @@ export class DatabaseDriver {
|
|
|
157
157
|
Object.assign(o, createOrderBy(key, direction[key]));
|
|
158
158
|
return o;
|
|
159
159
|
}, {});
|
|
160
|
-
return
|
|
160
|
+
return { [prop]: value };
|
|
161
161
|
}
|
|
162
162
|
const desc = direction === QueryOrderNumeric.DESC || direction.toString().toLowerCase() === 'desc';
|
|
163
163
|
const dir = Utils.xor(desc, isLast) ? 'desc' : 'asc';
|
|
164
|
-
return
|
|
164
|
+
return { [prop]: dir };
|
|
165
165
|
};
|
|
166
166
|
return {
|
|
167
167
|
orderBy: definition.map(([prop, direction]) => createOrderBy(prop, direction)),
|
|
@@ -256,7 +256,7 @@ export class DatabaseDriver {
|
|
|
256
256
|
else {
|
|
257
257
|
data[prop.fieldNames[0]] = this.mapDataToFieldNames(copy, stringifyJsonArrays, prop.embeddedProps, convertCustomTypes, true);
|
|
258
258
|
}
|
|
259
|
-
if (stringifyJsonArrays && prop.array) {
|
|
259
|
+
if (stringifyJsonArrays && prop.array && !object) {
|
|
260
260
|
data[prop.fieldNames[0]] = this.platform.convertJsonToDatabaseValue(data[prop.fieldNames[0]]);
|
|
261
261
|
}
|
|
262
262
|
return;
|
|
@@ -301,16 +301,23 @@ export class DatabaseDriver {
|
|
|
301
301
|
if (prop.joinColumns && Array.isArray(data[k])) {
|
|
302
302
|
const copy = Utils.flatten(data[k]);
|
|
303
303
|
delete data[k];
|
|
304
|
-
prop.joinColumns.forEach((joinColumn, idx) => data[joinColumn] = copy[idx]);
|
|
304
|
+
prop.joinColumns.forEach((joinColumn, idx) => (data[joinColumn] = copy[idx]));
|
|
305
305
|
return;
|
|
306
306
|
}
|
|
307
307
|
if (prop.joinColumns?.length > 1 && data[k] == null) {
|
|
308
308
|
delete data[k];
|
|
309
|
-
prop.ownColumns.forEach(joinColumn => data[joinColumn] = null);
|
|
309
|
+
prop.ownColumns.forEach(joinColumn => (data[joinColumn] = null));
|
|
310
310
|
return;
|
|
311
311
|
}
|
|
312
|
-
if (prop.customType &&
|
|
313
|
-
|
|
312
|
+
if (prop.customType &&
|
|
313
|
+
convertCustomTypes &&
|
|
314
|
+
!(prop.customType instanceof JsonType && object) &&
|
|
315
|
+
!isRaw(data[k])) {
|
|
316
|
+
data[k] = prop.customType.convertToDatabaseValue(data[k], this.platform, {
|
|
317
|
+
fromQuery: true,
|
|
318
|
+
key: k,
|
|
319
|
+
mode: 'query-data',
|
|
320
|
+
});
|
|
314
321
|
}
|
|
315
322
|
if (prop.hasConvertToDatabaseValueSQL && !prop.object && !isRaw(data[k])) {
|
|
316
323
|
const quoted = this.platform.quoteValue(data[k]);
|
|
@@ -390,7 +397,18 @@ export class DatabaseDriver {
|
|
|
390
397
|
createReplicas(cb) {
|
|
391
398
|
const replicas = this.config.get('replicas', []);
|
|
392
399
|
const ret = [];
|
|
393
|
-
const props = [
|
|
400
|
+
const props = [
|
|
401
|
+
'dbName',
|
|
402
|
+
'clientUrl',
|
|
403
|
+
'host',
|
|
404
|
+
'port',
|
|
405
|
+
'user',
|
|
406
|
+
'password',
|
|
407
|
+
'multipleStatements',
|
|
408
|
+
'pool',
|
|
409
|
+
'name',
|
|
410
|
+
'driverOptions',
|
|
411
|
+
];
|
|
394
412
|
for (const conf of replicas) {
|
|
395
413
|
const replicaConfig = Utils.copy(conf);
|
|
396
414
|
for (const prop of props) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ConnectionType, EntityData, EntityMetadata, EntityProperty, FilterQuery, Primary, Dictionary, IPrimaryKey, PopulateOptions, EntityDictionary, AutoPath, ObjectQuery, FilterObject, Populate, EntityName, PopulateHintOptions, Prefixes } from '../typings.js';
|
|
1
|
+
import type { ConnectionType, Constructor, EntityData, EntityMetadata, EntityProperty, FilterQuery, Primary, Dictionary, IPrimaryKey, PopulateOptions, EntityDictionary, AutoPath, ObjectQuery, FilterObject, Populate, EntityName, PopulateHintOptions, Prefixes } from '../typings.js';
|
|
2
2
|
import type { Connection, QueryResult, Transaction } from '../connections/Connection.js';
|
|
3
3
|
import type { FlushMode, LockMode, QueryOrderMap, QueryFlag, LoadStrategy, PopulateHint, PopulatePath } from '../enums.js';
|
|
4
4
|
import type { Platform } from '../platforms/Platform.js';
|
|
@@ -7,6 +7,7 @@ import type { Collection } from '../entity/Collection.js';
|
|
|
7
7
|
import type { EntityManager } from '../EntityManager.js';
|
|
8
8
|
import type { DriverException } from '../exceptions.js';
|
|
9
9
|
import type { Configuration } from '../utils/Configuration.js';
|
|
10
|
+
import type { MikroORM } from '../MikroORM.js';
|
|
10
11
|
import type { LoggingOptions, LogContext } from '../logging/Logger.js';
|
|
11
12
|
import type { Raw } from '../utils/RawQueryFragment.js';
|
|
12
13
|
export declare const EntityManagerType: unique symbol;
|
|
@@ -65,6 +66,10 @@ export interface IDatabaseDriver<C extends Connection = Connection> {
|
|
|
65
66
|
schema?: string;
|
|
66
67
|
parentSchema?: string;
|
|
67
68
|
}): string | undefined;
|
|
69
|
+
/**
|
|
70
|
+
* @internal
|
|
71
|
+
*/
|
|
72
|
+
getORMClass(): Constructor<MikroORM>;
|
|
68
73
|
}
|
|
69
74
|
export type EntityField<T, P extends string = PopulatePath.ALL> = keyof T | PopulatePath.ALL | AutoPath<T, P, `${PopulatePath.ALL}`>;
|
|
70
75
|
export type OrderDefinition<T> = (QueryOrderMap<T> & {
|
|
@@ -105,6 +110,22 @@ export interface FindOptions<Entity, Hint extends string = never, Fields extends
|
|
|
105
110
|
* when nesting the condition. This is used for implementation of joined filters.
|
|
106
111
|
*/
|
|
107
112
|
populateFilter?: ObjectQuery<Entity>;
|
|
113
|
+
/**
|
|
114
|
+
* Index-friendly alternative to `$or` for conditions that span joined relations.
|
|
115
|
+
* Each array element becomes an independent branch combined via `UNION ALL` subquery:
|
|
116
|
+
* `WHERE pk IN (branch_1 UNION ALL branch_2 ... branch_N)`.
|
|
117
|
+
* The database plans each branch independently, enabling per-table index usage
|
|
118
|
+
* (e.g. GIN trigram indexes for fuzzy search across related entities).
|
|
119
|
+
* sql only
|
|
120
|
+
*/
|
|
121
|
+
unionWhere?: ObjectQuery<Entity>[];
|
|
122
|
+
/**
|
|
123
|
+
* Strategy for combining `unionWhere` branches.
|
|
124
|
+
* - `'union-all'` (default) — skips deduplication, faster for most use cases.
|
|
125
|
+
* - `'union'` — deduplicates rows between branches; useful when branch overlap is very high.
|
|
126
|
+
* sql only
|
|
127
|
+
*/
|
|
128
|
+
unionWhereStrategy?: 'union-all' | 'union';
|
|
108
129
|
/** Used for ordering of the populate queries. If not specified, the value of `options.orderBy` is used. */
|
|
109
130
|
populateOrderBy?: OrderDefinition<Entity>;
|
|
110
131
|
/** Per-relation overrides for populate loading behavior. Keys are populate paths (same as used in `populate`). */
|
|
@@ -194,6 +215,13 @@ export interface NativeInsertUpdateOptions<T> {
|
|
|
194
215
|
/** `nativeUpdate()` only option */
|
|
195
216
|
upsert?: boolean;
|
|
196
217
|
loggerContext?: LogContext;
|
|
218
|
+
/** sql only */
|
|
219
|
+
unionWhere?: ObjectQuery<T>[];
|
|
220
|
+
/** sql only */
|
|
221
|
+
unionWhereStrategy?: 'union-all' | 'union';
|
|
222
|
+
filters?: FilterOptions;
|
|
223
|
+
/** @internal */
|
|
224
|
+
em?: EntityManager;
|
|
197
225
|
}
|
|
198
226
|
export interface NativeInsertUpdateManyOptions<T> extends NativeInsertUpdateOptions<T> {
|
|
199
227
|
processCollections?: boolean;
|
|
@@ -218,6 +246,10 @@ export interface CountOptions<T extends object, P extends string = never> {
|
|
|
218
246
|
populate?: Populate<T, P>;
|
|
219
247
|
populateWhere?: ObjectQuery<T> | PopulateHint | `${PopulateHint}`;
|
|
220
248
|
populateFilter?: ObjectQuery<T>;
|
|
249
|
+
/** @see FindOptions.unionWhere */
|
|
250
|
+
unionWhere?: ObjectQuery<T>[];
|
|
251
|
+
/** @see FindOptions.unionWhereStrategy */
|
|
252
|
+
unionWhereStrategy?: 'union-all' | 'union';
|
|
221
253
|
ctx?: Transaction;
|
|
222
254
|
connectionType?: ConnectionType;
|
|
223
255
|
flushMode?: FlushMode | `${FlushMode}`;
|
|
@@ -240,12 +272,28 @@ export interface UpdateOptions<T> {
|
|
|
240
272
|
filters?: FilterOptions;
|
|
241
273
|
schema?: string;
|
|
242
274
|
ctx?: Transaction;
|
|
275
|
+
/** sql only */
|
|
276
|
+
unionWhere?: ObjectQuery<T>[];
|
|
277
|
+
/** sql only */
|
|
278
|
+
unionWhereStrategy?: 'union-all' | 'union';
|
|
243
279
|
}
|
|
244
280
|
export interface DeleteOptions<T> extends DriverMethodOptions {
|
|
245
281
|
filters?: FilterOptions;
|
|
282
|
+
/** sql only */
|
|
283
|
+
unionWhere?: ObjectQuery<T>[];
|
|
284
|
+
/** sql only */
|
|
285
|
+
unionWhereStrategy?: 'union-all' | 'union';
|
|
286
|
+
/** @internal */
|
|
287
|
+
em?: EntityManager;
|
|
246
288
|
}
|
|
247
289
|
export interface NativeDeleteOptions<T> extends DriverMethodOptions {
|
|
248
290
|
filters?: FilterOptions;
|
|
291
|
+
/** sql only */
|
|
292
|
+
unionWhere?: ObjectQuery<T>[];
|
|
293
|
+
/** sql only */
|
|
294
|
+
unionWhereStrategy?: 'union-all' | 'union';
|
|
295
|
+
/** @internal */
|
|
296
|
+
em?: EntityManager;
|
|
249
297
|
}
|
|
250
298
|
export interface LockOptions extends DriverMethodOptions {
|
|
251
299
|
lockMode?: LockMode;
|