@mikro-orm/core 7.0.0-rc.2 → 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 +2 -1
- package/EntityManager.js +106 -42
- 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 +43 -0
- package/entity/Collection.js +43 -17
- package/entity/EntityAssigner.js +23 -11
- package/entity/EntityFactory.js +32 -12
- package/entity/EntityHelper.js +25 -16
- package/entity/EntityLoader.js +55 -22
- package/entity/Reference.d.ts +1 -1
- package/entity/Reference.js +37 -8
- package/entity/WrappedEntity.js +5 -1
- package/entity/defineEntity.d.ts +24 -12
- 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 +1 -1
- 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 +106 -47
- 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 +38 -38
- package/platforms/Platform.d.ts +1 -0
- package/platforms/Platform.js +49 -23
- package/serialization/EntitySerializer.js +7 -3
- package/serialization/SerializationContext.js +1 -1
- package/typings.d.ts +23 -23
- package/typings.js +9 -9
- package/unit-of-work/ChangeSet.js +4 -4
- package/unit-of-work/ChangeSetComputer.js +8 -6
- package/unit-of-work/ChangeSetPersister.js +13 -8
- 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 +1 -1
- package/utils/AbstractMigrator.js +3 -5
- package/utils/AbstractSchemaGenerator.js +2 -1
- package/utils/AsyncContext.js +1 -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.js +2 -5
- 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>;
|
|
@@ -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
|
@@ -123,7 +123,7 @@ export class EntityManager {
|
|
|
123
123
|
else {
|
|
124
124
|
options.orderBy ??= {};
|
|
125
125
|
}
|
|
126
|
-
options.populate = await em.preparePopulate(entityName, options);
|
|
126
|
+
options.populate = (await em.preparePopulate(entityName, options));
|
|
127
127
|
const populate = options.populate;
|
|
128
128
|
const cacheKey = em.cacheKey(entityName, options, 'em.find', where);
|
|
129
129
|
const cached = await em.tryCache(entityName, options.cache, cacheKey, options.refresh, true);
|
|
@@ -141,6 +141,7 @@ export class EntityManager {
|
|
|
141
141
|
options._populateWhere = options.populateWhere ?? this.config.get('populateWhere');
|
|
142
142
|
options.populateWhere = this.createPopulateWhere({ ...where }, options);
|
|
143
143
|
options.populateFilter = await this.getJoinedFilters(meta, options);
|
|
144
|
+
await em.processUnionWhere(entityName, options, 'read');
|
|
144
145
|
const results = await em.driver.find(entityName, where, { ctx: em.transactionContext, em, ...options });
|
|
145
146
|
if (results.length === 0) {
|
|
146
147
|
await em.storeCache(options.cache, cached, []);
|
|
@@ -194,10 +195,10 @@ export class EntityManager {
|
|
|
194
195
|
em.prepareOptions(options);
|
|
195
196
|
options.strategy = 'joined';
|
|
196
197
|
await em.tryFlush(entityName, options);
|
|
197
|
-
const where = await em.processWhere(entityName, options.where ?? {}, options, 'read');
|
|
198
|
+
const where = (await em.processWhere(entityName, options.where ?? {}, options, 'read'));
|
|
198
199
|
validateParams(where);
|
|
199
200
|
options.orderBy = options.orderBy || {};
|
|
200
|
-
options.populate = await em.preparePopulate(entityName, options);
|
|
201
|
+
options.populate = (await em.preparePopulate(entityName, options));
|
|
201
202
|
const meta = this.metadata.get(entityName);
|
|
202
203
|
options = { ...options };
|
|
203
204
|
// save the original hint value so we know it was infer/all
|
|
@@ -298,6 +299,14 @@ export class EntityManager {
|
|
|
298
299
|
where = this.applyDiscriminatorCondition(entityName, where);
|
|
299
300
|
return where;
|
|
300
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
|
+
}
|
|
301
310
|
// this method only handles the problem for mongo driver, SQL drivers have their implementation inside QueryBuilder
|
|
302
311
|
applyDiscriminatorCondition(entityName, where) {
|
|
303
312
|
const meta = this.metadata.find(entityName);
|
|
@@ -314,7 +323,10 @@ export class EntityManager {
|
|
|
314
323
|
};
|
|
315
324
|
lookUpChildren(children, meta.class);
|
|
316
325
|
/* v8 ignore next */
|
|
317
|
-
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;
|
|
318
330
|
return where;
|
|
319
331
|
}
|
|
320
332
|
createPopulateWhere(cond, options) {
|
|
@@ -381,10 +393,11 @@ export class EntityManager {
|
|
|
381
393
|
}
|
|
382
394
|
const ret = options.populate;
|
|
383
395
|
for (const prop of meta.relations) {
|
|
384
|
-
if (prop.object
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
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)) {
|
|
388
401
|
continue;
|
|
389
402
|
}
|
|
390
403
|
options = { ...options, filters: QueryHelper.mergePropertyFilters(prop.filters, options.filters) };
|
|
@@ -424,9 +437,7 @@ export class EntityManager {
|
|
|
424
437
|
const ret = [];
|
|
425
438
|
const active = new Set();
|
|
426
439
|
const push = (source) => {
|
|
427
|
-
const activeFilters = QueryHelper
|
|
428
|
-
.getActiveFilters(meta, options, source)
|
|
429
|
-
.filter(f => !active.has(f.name));
|
|
440
|
+
const activeFilters = QueryHelper.getActiveFilters(meta, options, source).filter(f => !active.has(f.name));
|
|
430
441
|
filters.push(...activeFilters);
|
|
431
442
|
activeFilters.forEach(f => active.add(f.name));
|
|
432
443
|
};
|
|
@@ -440,6 +451,7 @@ export class EntityManager {
|
|
|
440
451
|
let cond;
|
|
441
452
|
if (filter.cond instanceof Function) {
|
|
442
453
|
// @ts-ignore
|
|
454
|
+
// oxfmt-ignore
|
|
443
455
|
const args = Utils.isPlainObject(options?.[filter.name]) ? options[filter.name] : this.getContext().filterParams[filter.name];
|
|
444
456
|
if (!args && filter.cond.length > 0 && filter.args !== false) {
|
|
445
457
|
throw new Error(`No arguments provided for filter '${filter.name}'`);
|
|
@@ -582,12 +594,18 @@ export class EntityManager {
|
|
|
582
594
|
for (const e of fork.unitOfWork.getIdentityMap()) {
|
|
583
595
|
const ref = em.getReference(e.constructor, helper(e).getPrimaryKey());
|
|
584
596
|
const data = helper(e).serialize({ ignoreSerializers: true, includeHidden: true, convertCustomTypes: false });
|
|
585
|
-
em.config
|
|
597
|
+
em.config
|
|
598
|
+
.getHydrator(this.metadata)
|
|
599
|
+
.hydrate(ref, helper(ref).__meta, data, em.entityFactory, 'full', false, false);
|
|
586
600
|
Utils.merge(helper(ref).__originalEntityData, this.comparator.prepareEntity(e));
|
|
587
601
|
found ||= ref === entity;
|
|
588
602
|
}
|
|
589
603
|
if (!found) {
|
|
590
|
-
const data = helper(reloaded).serialize({
|
|
604
|
+
const data = helper(reloaded).serialize({
|
|
605
|
+
ignoreSerializers: true,
|
|
606
|
+
includeHidden: true,
|
|
607
|
+
convertCustomTypes: true,
|
|
608
|
+
});
|
|
591
609
|
em.config.getHydrator(this.metadata).hydrate(entity, wrapped.__meta, data, em.entityFactory, 'full', false, true);
|
|
592
610
|
Utils.merge(wrapped.__originalEntityData, this.comparator.prepareEntity(reloaded));
|
|
593
611
|
}
|
|
@@ -623,7 +641,7 @@ export class EntityManager {
|
|
|
623
641
|
return em.lockAndPopulate(meta, entity, where, options);
|
|
624
642
|
}
|
|
625
643
|
validateParams(where);
|
|
626
|
-
options.populate = await em.preparePopulate(entityName, options);
|
|
644
|
+
options.populate = (await em.preparePopulate(entityName, options));
|
|
627
645
|
const cacheKey = em.cacheKey(entityName, options, 'em.findOne', where);
|
|
628
646
|
const cached = await em.tryCache(entityName, options.cache, cacheKey, options.refresh, true);
|
|
629
647
|
if (cached?.data !== undefined) {
|
|
@@ -642,6 +660,7 @@ export class EntityManager {
|
|
|
642
660
|
options._populateWhere = options.populateWhere ?? this.config.get('populateWhere');
|
|
643
661
|
options.populateWhere = this.createPopulateWhere({ ...where }, options);
|
|
644
662
|
options.populateFilter = await this.getJoinedFilters(meta, options);
|
|
663
|
+
await em.processUnionWhere(entityName, options, 'read');
|
|
645
664
|
const data = await em.driver.findOne(entityName, where, {
|
|
646
665
|
ctx: em.transactionContext,
|
|
647
666
|
em,
|
|
@@ -770,9 +789,12 @@ export class EntityManager {
|
|
|
770
789
|
initialized: true,
|
|
771
790
|
schema: options.schema,
|
|
772
791
|
});
|
|
773
|
-
const uniqueFields = options.onConflictFields ??
|
|
792
|
+
const uniqueFields = options.onConflictFields ??
|
|
793
|
+
(Utils.isPlainObject(where) ? Object.keys(where) : meta.primaryKeys);
|
|
774
794
|
const returning = getOnConflictReturningFields(meta, data, uniqueFields, options);
|
|
775
|
-
if (options.onConflictAction === 'ignore' ||
|
|
795
|
+
if (options.onConflictAction === 'ignore' ||
|
|
796
|
+
!helper(entity).hasPrimaryKey() ||
|
|
797
|
+
(returning.length > 0 && !(this.getPlatform().usesReturningStatement() && ret.row))) {
|
|
776
798
|
const where = {};
|
|
777
799
|
if (Array.isArray(uniqueFields)) {
|
|
778
800
|
for (const prop of uniqueFields) {
|
|
@@ -896,7 +918,9 @@ export class EntityManager {
|
|
|
896
918
|
}
|
|
897
919
|
}
|
|
898
920
|
const unique = options.onConflictFields ?? meta.props.filter(p => p.unique).map(p => p.name);
|
|
899
|
-
propIndex =
|
|
921
|
+
propIndex =
|
|
922
|
+
!isRaw(unique) &&
|
|
923
|
+
unique.findIndex(p => data[p] ?? data[p.substring(0, p.indexOf('.'))] != null);
|
|
900
924
|
const tmp = getWhereCondition(meta, options.onConflictFields, row, where);
|
|
901
925
|
propIndex = tmp.propIndex;
|
|
902
926
|
where = QueryHelper.processWhere({
|
|
@@ -929,12 +953,16 @@ export class EntityManager {
|
|
|
929
953
|
entitiesByData.clear();
|
|
930
954
|
const loadPK = new Map();
|
|
931
955
|
allData.forEach((row, i) => {
|
|
932
|
-
em.unitOfWork
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
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
|
+
});
|
|
938
966
|
if (!helper(entity).hasPrimaryKey()) {
|
|
939
967
|
loadPK.set(entity, allWhere[i]);
|
|
940
968
|
}
|
|
@@ -942,12 +970,15 @@ export class EntityManager {
|
|
|
942
970
|
entitiesByData.set(row, entity);
|
|
943
971
|
});
|
|
944
972
|
// skip if we got the PKs via returning statement (`rows`)
|
|
973
|
+
// oxfmt-ignore
|
|
945
974
|
const uniqueFields = options.onConflictFields ?? (Utils.isPlainObject(allWhere[0]) ? Object.keys(allWhere[0]).flatMap(key => Utils.splitPrimaryKeys(key)) : meta.primaryKeys);
|
|
946
975
|
const returning = getOnConflictReturningFields(meta, data[0], uniqueFields, options);
|
|
947
976
|
const reloadFields = returning.length > 0 && !(this.getPlatform().usesReturningStatement() && res.rows?.length);
|
|
948
977
|
if (options.onConflictAction === 'ignore' || (!res.rows?.length && loadPK.size > 0) || reloadFields) {
|
|
949
978
|
const unique = meta.hydrateProps.filter(p => !p.lazy).map(p => p.name);
|
|
950
|
-
const add = new Set(propIndex !== false && propIndex >= 0
|
|
979
|
+
const add = new Set(propIndex !== false && propIndex >= 0
|
|
980
|
+
? [unique[propIndex]]
|
|
981
|
+
: []);
|
|
951
982
|
for (const cond of loadPK.values()) {
|
|
952
983
|
Utils.keys(cond).forEach(key => add.add(key));
|
|
953
984
|
}
|
|
@@ -960,7 +991,9 @@ export class EntityManager {
|
|
|
960
991
|
});
|
|
961
992
|
});
|
|
962
993
|
const data2 = await this.driver.find(meta.class, where, {
|
|
963
|
-
fields: returning
|
|
994
|
+
fields: returning
|
|
995
|
+
.concat(...add)
|
|
996
|
+
.concat(...(Array.isArray(uniqueFields) ? uniqueFields : [])),
|
|
964
997
|
ctx: em.transactionContext,
|
|
965
998
|
convertCustomTypes: true,
|
|
966
999
|
connectionType: 'write',
|
|
@@ -1136,7 +1169,10 @@ export class EntityManager {
|
|
|
1136
1169
|
}
|
|
1137
1170
|
data = QueryHelper.processObjectParams(data);
|
|
1138
1171
|
validateParams(data, 'insert data');
|
|
1139
|
-
const res = await em.driver.nativeInsert(entityName, data, {
|
|
1172
|
+
const res = await em.driver.nativeInsert(entityName, data, {
|
|
1173
|
+
ctx: em.transactionContext,
|
|
1174
|
+
...options,
|
|
1175
|
+
});
|
|
1140
1176
|
return res.insertId;
|
|
1141
1177
|
}
|
|
1142
1178
|
/**
|
|
@@ -1176,7 +1212,10 @@ export class EntityManager {
|
|
|
1176
1212
|
}
|
|
1177
1213
|
data = data.map(row => QueryHelper.processObjectParams(row));
|
|
1178
1214
|
data.forEach(row => validateParams(row, 'insert data'));
|
|
1179
|
-
const res = await em.driver.nativeInsertMany(entityName, data, {
|
|
1215
|
+
const res = await em.driver.nativeInsertMany(entityName, data, {
|
|
1216
|
+
ctx: em.transactionContext,
|
|
1217
|
+
...options,
|
|
1218
|
+
});
|
|
1180
1219
|
if (res.insertedIds) {
|
|
1181
1220
|
return res.insertedIds;
|
|
1182
1221
|
}
|
|
@@ -1188,11 +1227,16 @@ export class EntityManager {
|
|
|
1188
1227
|
async nativeUpdate(entityName, where, data, options = {}) {
|
|
1189
1228
|
const em = this.getContext(false);
|
|
1190
1229
|
em.prepareOptions(options);
|
|
1230
|
+
await em.processUnionWhere(entityName, options, 'update');
|
|
1191
1231
|
data = QueryHelper.processObjectParams(data);
|
|
1192
1232
|
where = await em.processWhere(entityName, where, { ...options, convertCustomTypes: false }, 'update');
|
|
1193
1233
|
validateParams(data, 'update data');
|
|
1194
1234
|
validateParams(where, 'update condition');
|
|
1195
|
-
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
|
+
});
|
|
1196
1240
|
return res.affectedRows;
|
|
1197
1241
|
}
|
|
1198
1242
|
/**
|
|
@@ -1201,9 +1245,14 @@ export class EntityManager {
|
|
|
1201
1245
|
async nativeDelete(entityName, where, options = {}) {
|
|
1202
1246
|
const em = this.getContext(false);
|
|
1203
1247
|
em.prepareOptions(options);
|
|
1204
|
-
|
|
1248
|
+
await em.processUnionWhere(entityName, options, 'delete');
|
|
1249
|
+
where = (await em.processWhere(entityName, where, options, 'delete'));
|
|
1205
1250
|
validateParams(where, 'delete condition');
|
|
1206
|
-
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
|
+
});
|
|
1207
1256
|
return res.affectedRows;
|
|
1208
1257
|
}
|
|
1209
1258
|
/**
|
|
@@ -1214,7 +1263,10 @@ export class EntityManager {
|
|
|
1214
1263
|
const data = this.driver.mapResult(result, meta);
|
|
1215
1264
|
for (const k of Object.keys(data)) {
|
|
1216
1265
|
const prop = meta.properties[k];
|
|
1217
|
-
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)) {
|
|
1218
1270
|
validateProperty(prop, data[k], data);
|
|
1219
1271
|
}
|
|
1220
1272
|
}
|
|
@@ -1243,7 +1295,9 @@ export class EntityManager {
|
|
|
1243
1295
|
return entity;
|
|
1244
1296
|
}
|
|
1245
1297
|
const dataIsEntity = Utils.isEntity(data);
|
|
1246
|
-
entity = dataIsEntity
|
|
1298
|
+
entity = dataIsEntity
|
|
1299
|
+
? data
|
|
1300
|
+
: em.entityFactory.create(entityName, data, { merge: true, ...options });
|
|
1247
1301
|
const visited = options.cascade ? undefined : new Set([entity]);
|
|
1248
1302
|
em.unitOfWork.merge(entity, visited);
|
|
1249
1303
|
return entity;
|
|
@@ -1313,7 +1367,7 @@ export class EntityManager {
|
|
|
1313
1367
|
em.prepareOptions(options);
|
|
1314
1368
|
await em.tryFlush(entityName, options);
|
|
1315
1369
|
where = await em.processWhere(entityName, where, options, 'read');
|
|
1316
|
-
options.populate = await em.preparePopulate(entityName, options);
|
|
1370
|
+
options.populate = (await em.preparePopulate(entityName, options));
|
|
1317
1371
|
options = { ...options };
|
|
1318
1372
|
// save the original hint value so we know it was infer/all
|
|
1319
1373
|
const meta = em.metadata.find(entityName);
|
|
@@ -1322,6 +1376,7 @@ export class EntityManager {
|
|
|
1322
1376
|
options.populateFilter = await this.getJoinedFilters(meta, options);
|
|
1323
1377
|
validateParams(where);
|
|
1324
1378
|
delete options.orderBy;
|
|
1379
|
+
await em.processUnionWhere(entityName, options, 'read');
|
|
1325
1380
|
const cacheKey = em.cacheKey(entityName, options, 'em.count', where);
|
|
1326
1381
|
const cached = await em.tryCache(entityName, options.cache, cacheKey);
|
|
1327
1382
|
if (cached?.data !== undefined) {
|
|
@@ -1459,7 +1514,8 @@ export class EntityManager {
|
|
|
1459
1514
|
em.config.set('allowGlobalContext', true);
|
|
1460
1515
|
const fork = new em.constructor(em.config, em.driver, em.metadata, options.useContext, eventManager);
|
|
1461
1516
|
fork.setFlushMode(options.flushMode ?? em.flushMode);
|
|
1462
|
-
fork.disableTransactions =
|
|
1517
|
+
fork.disableTransactions =
|
|
1518
|
+
options.disableTransactions ?? this.disableTransactions ?? this.config.get('disableTransactions');
|
|
1463
1519
|
em.config.set('allowGlobalContext', allowGlobalContext);
|
|
1464
1520
|
if (options.keepTransactionContext) {
|
|
1465
1521
|
fork.transactionContext = em.transactionContext;
|
|
@@ -1626,11 +1682,14 @@ export class EntityManager {
|
|
|
1626
1682
|
const ret = [];
|
|
1627
1683
|
for (let field of fields) {
|
|
1628
1684
|
if (field === PopulatePath.ALL || field.startsWith(`${PopulatePath.ALL}.`)) {
|
|
1629
|
-
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));
|
|
1630
1688
|
continue;
|
|
1631
1689
|
}
|
|
1632
1690
|
field = field.split(':')[0];
|
|
1633
|
-
if (!field.includes('.') &&
|
|
1691
|
+
if (!field.includes('.') &&
|
|
1692
|
+
![ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(meta.properties[field].kind)) {
|
|
1634
1693
|
ret.push(field);
|
|
1635
1694
|
continue;
|
|
1636
1695
|
}
|
|
@@ -1659,7 +1718,8 @@ export class EntityManager {
|
|
|
1659
1718
|
return populate;
|
|
1660
1719
|
}
|
|
1661
1720
|
if (typeof options.populate !== 'boolean') {
|
|
1662
|
-
options.populate = Utils.asArray(options.populate)
|
|
1721
|
+
options.populate = Utils.asArray(options.populate)
|
|
1722
|
+
.map(field => {
|
|
1663
1723
|
/* v8 ignore next */
|
|
1664
1724
|
if (typeof field === 'boolean' || field === PopulatePath.ALL) {
|
|
1665
1725
|
return [{ field: meta.primaryKeys[0], strategy: options.strategy, all: !!field }]; //
|
|
@@ -1674,7 +1734,8 @@ export class EntityManager {
|
|
|
1674
1734
|
return [{ field, strategy: options.strategy }];
|
|
1675
1735
|
}
|
|
1676
1736
|
return [field];
|
|
1677
|
-
})
|
|
1737
|
+
})
|
|
1738
|
+
.flat();
|
|
1678
1739
|
}
|
|
1679
1740
|
const populate = this.entityLoader.normalizePopulate(entityName, options.populate, options.strategy, true, options.exclude);
|
|
1680
1741
|
const invalid = populate.find(({ field }) => !this.canPopulate(entityName, field));
|
|
@@ -1776,7 +1837,7 @@ export class EntityManager {
|
|
|
1776
1837
|
config ??= this.config.get('resultCache').global;
|
|
1777
1838
|
if (config) {
|
|
1778
1839
|
const em = this.getContext();
|
|
1779
|
-
const expiration = Array.isArray(config) ? config[1] :
|
|
1840
|
+
const expiration = Array.isArray(config) ? config[1] : typeof config === 'number' ? config : undefined;
|
|
1780
1841
|
await em.resultCache.set(key.key, data instanceof Function ? data() : data, '', expiration);
|
|
1781
1842
|
}
|
|
1782
1843
|
}
|
|
@@ -1818,9 +1879,12 @@ export class EntityManager {
|
|
|
1818
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) {
|
|
@@ -110,6 +110,22 @@ export interface FindOptions<Entity, Hint extends string = never, Fields extends
|
|
|
110
110
|
* when nesting the condition. This is used for implementation of joined filters.
|
|
111
111
|
*/
|
|
112
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';
|
|
113
129
|
/** Used for ordering of the populate queries. If not specified, the value of `options.orderBy` is used. */
|
|
114
130
|
populateOrderBy?: OrderDefinition<Entity>;
|
|
115
131
|
/** Per-relation overrides for populate loading behavior. Keys are populate paths (same as used in `populate`). */
|
|
@@ -199,6 +215,13 @@ export interface NativeInsertUpdateOptions<T> {
|
|
|
199
215
|
/** `nativeUpdate()` only option */
|
|
200
216
|
upsert?: boolean;
|
|
201
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;
|
|
202
225
|
}
|
|
203
226
|
export interface NativeInsertUpdateManyOptions<T> extends NativeInsertUpdateOptions<T> {
|
|
204
227
|
processCollections?: boolean;
|
|
@@ -223,6 +246,10 @@ export interface CountOptions<T extends object, P extends string = never> {
|
|
|
223
246
|
populate?: Populate<T, P>;
|
|
224
247
|
populateWhere?: ObjectQuery<T> | PopulateHint | `${PopulateHint}`;
|
|
225
248
|
populateFilter?: ObjectQuery<T>;
|
|
249
|
+
/** @see FindOptions.unionWhere */
|
|
250
|
+
unionWhere?: ObjectQuery<T>[];
|
|
251
|
+
/** @see FindOptions.unionWhereStrategy */
|
|
252
|
+
unionWhereStrategy?: 'union-all' | 'union';
|
|
226
253
|
ctx?: Transaction;
|
|
227
254
|
connectionType?: ConnectionType;
|
|
228
255
|
flushMode?: FlushMode | `${FlushMode}`;
|
|
@@ -245,12 +272,28 @@ export interface UpdateOptions<T> {
|
|
|
245
272
|
filters?: FilterOptions;
|
|
246
273
|
schema?: string;
|
|
247
274
|
ctx?: Transaction;
|
|
275
|
+
/** sql only */
|
|
276
|
+
unionWhere?: ObjectQuery<T>[];
|
|
277
|
+
/** sql only */
|
|
278
|
+
unionWhereStrategy?: 'union-all' | 'union';
|
|
248
279
|
}
|
|
249
280
|
export interface DeleteOptions<T> extends DriverMethodOptions {
|
|
250
281
|
filters?: FilterOptions;
|
|
282
|
+
/** sql only */
|
|
283
|
+
unionWhere?: ObjectQuery<T>[];
|
|
284
|
+
/** sql only */
|
|
285
|
+
unionWhereStrategy?: 'union-all' | 'union';
|
|
286
|
+
/** @internal */
|
|
287
|
+
em?: EntityManager;
|
|
251
288
|
}
|
|
252
289
|
export interface NativeDeleteOptions<T> extends DriverMethodOptions {
|
|
253
290
|
filters?: FilterOptions;
|
|
291
|
+
/** sql only */
|
|
292
|
+
unionWhere?: ObjectQuery<T>[];
|
|
293
|
+
/** sql only */
|
|
294
|
+
unionWhereStrategy?: 'union-all' | 'union';
|
|
295
|
+
/** @internal */
|
|
296
|
+
em?: EntityManager;
|
|
254
297
|
}
|
|
255
298
|
export interface LockOptions extends DriverMethodOptions {
|
|
256
299
|
lockMode?: LockMode;
|