@mikro-orm/core 7.2.2-dev.8 → 7.3.0-dev.0
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 +6 -1
- package/EntityManager.js +49 -26
- package/package.json +1 -1
- package/unit-of-work/ChangeSetPersister.js +9 -7
- package/unit-of-work/UnitOfWork.js +4 -1
- package/utils/QueryHelper.js +9 -1
- package/utils/TransactionContext.d.ts +1 -0
- package/utils/TransactionContext.js +9 -3
- package/utils/TransactionManager.js +2 -2
- package/utils/Utils.js +1 -1
package/EntityManager.d.ts
CHANGED
|
@@ -580,6 +580,11 @@ export declare class EntityManager<Driver extends IDatabaseDriver = IDatabaseDri
|
|
|
580
580
|
* Checks whether this EntityManager is currently operating inside a database transaction.
|
|
581
581
|
*/
|
|
582
582
|
isInTransaction(): boolean;
|
|
583
|
+
/**
|
|
584
|
+
* Checks whether transactions are disabled for this EntityManager.
|
|
585
|
+
* @internal
|
|
586
|
+
*/
|
|
587
|
+
isTransactionDisabled(): boolean;
|
|
583
588
|
/**
|
|
584
589
|
* Gets the transaction context (driver dependent object used to make sure queries are executed on same connection).
|
|
585
590
|
*/
|
|
@@ -633,7 +638,7 @@ export declare class EntityManager<Driver extends IDatabaseDriver = IDatabaseDri
|
|
|
633
638
|
/**
|
|
634
639
|
* @internal
|
|
635
640
|
*/
|
|
636
|
-
tryCache<T extends object, R>(entityName: EntityName<T>, config: boolean | number | [string, number] | undefined, key: unknown, refresh?: boolean, merge?: boolean): Promise<{
|
|
641
|
+
tryCache<T extends object, R>(entityName: EntityName<T>, config: boolean | number | [string, number] | undefined, key: unknown, refresh?: boolean, merge?: boolean, schema?: string): Promise<{
|
|
637
642
|
data?: R | null;
|
|
638
643
|
key: string;
|
|
639
644
|
} | undefined>;
|
package/EntityManager.js
CHANGED
|
@@ -133,7 +133,7 @@ export class EntityManager {
|
|
|
133
133
|
options.populate = (await em.preparePopulate(entityName, options));
|
|
134
134
|
const populate = options.populate;
|
|
135
135
|
const cacheKey = em.cacheKey(entityName, options, 'em.find', where);
|
|
136
|
-
const cached = await em.tryCache(entityName, options.cache, cacheKey, options.refresh, true);
|
|
136
|
+
const cached = await em.tryCache(entityName, options.cache, cacheKey, options.refresh, true, options.schema);
|
|
137
137
|
if (cached?.data) {
|
|
138
138
|
await em.#entityLoader.populate(entityName, cached.data, populate, {
|
|
139
139
|
...options,
|
|
@@ -834,7 +834,7 @@ export class EntityManager {
|
|
|
834
834
|
validateParams(where);
|
|
835
835
|
options.populate = (await em.preparePopulate(entityName, options));
|
|
836
836
|
const cacheKey = em.cacheKey(entityName, options, 'em.findOne', where);
|
|
837
|
-
const cached = await em.tryCache(entityName, options.cache, cacheKey, options.refresh, true);
|
|
837
|
+
const cached = await em.tryCache(entityName, options.cache, cacheKey, options.refresh, true, options.schema);
|
|
838
838
|
if (cached?.data !== undefined) {
|
|
839
839
|
if (cached.data) {
|
|
840
840
|
await em.#entityLoader.populate(entityName, [cached.data], options.populate, {
|
|
@@ -1110,7 +1110,18 @@ export class EntityManager {
|
|
|
1110
1110
|
const ret = [];
|
|
1111
1111
|
for (const idx of shapes.values()) {
|
|
1112
1112
|
const res = await this.upsertMany(entityName, idx.map(i => data[i]), options);
|
|
1113
|
-
|
|
1113
|
+
const seen = new Set();
|
|
1114
|
+
let j = 0;
|
|
1115
|
+
// `res` holds every entity once, so skip the rows resolving to an entity an earlier row already took
|
|
1116
|
+
for (const i of idx) {
|
|
1117
|
+
const row = data[i];
|
|
1118
|
+
const entity = Utils.isEntity(row)
|
|
1119
|
+
? row
|
|
1120
|
+
: em.#unitOfWork.tryGetById(entityName, row, options.schema, false);
|
|
1121
|
+
if (!seen.has(entity)) {
|
|
1122
|
+
seen.add((ret[i] = res[j++]));
|
|
1123
|
+
}
|
|
1124
|
+
}
|
|
1114
1125
|
}
|
|
1115
1126
|
// rows resolving to the same entity are returned once, as in the single statement path
|
|
1116
1127
|
return [...new Set(ret.filter(entity => entity))];
|
|
@@ -1123,9 +1134,10 @@ export class EntityManager {
|
|
|
1123
1134
|
const generatedFields = new Set();
|
|
1124
1135
|
const allData = [];
|
|
1125
1136
|
const allWhere = [];
|
|
1137
|
+
const dataIndexes = [];
|
|
1138
|
+
const result = [];
|
|
1126
1139
|
const entities = new Map();
|
|
1127
1140
|
const entitiesByData = new Map();
|
|
1128
|
-
const entitiesByAllDataIdx = new Map();
|
|
1129
1141
|
for (let i = 0; i < data.length; i++) {
|
|
1130
1142
|
let row = data[i];
|
|
1131
1143
|
let where;
|
|
@@ -1133,14 +1145,12 @@ export class EntityManager {
|
|
|
1133
1145
|
const entity = row;
|
|
1134
1146
|
if (helper(entity).__managed && helper(entity).__em === em && !this.config.get('upsertManaged')) {
|
|
1135
1147
|
em.#entityFactory.mergeData(meta, entity, row, { initialized: true });
|
|
1136
|
-
|
|
1137
|
-
entitiesByData.set(row, entity);
|
|
1148
|
+
result[i] = entity;
|
|
1138
1149
|
continue;
|
|
1139
1150
|
}
|
|
1140
1151
|
where = helper(entity).getPrimaryKey();
|
|
1141
1152
|
getOnCreateGeneratedFields(meta, entity).forEach(field => generatedFields.add(field));
|
|
1142
1153
|
em.#entityFactory.assignDefaultValues(entity, meta);
|
|
1143
|
-
entitiesByAllDataIdx.set(allData.length, entity);
|
|
1144
1154
|
row = em.#comparator.prepareEntity(entity);
|
|
1145
1155
|
}
|
|
1146
1156
|
else {
|
|
@@ -1150,8 +1160,7 @@ export class EntityManager {
|
|
|
1150
1160
|
const exists = em.#unitOfWork.getById(entityName, where, options.schema);
|
|
1151
1161
|
if (exists) {
|
|
1152
1162
|
em.assign(exists, row);
|
|
1153
|
-
|
|
1154
|
-
entitiesByData.set(row, exists);
|
|
1163
|
+
result[i] = exists;
|
|
1155
1164
|
continue;
|
|
1156
1165
|
}
|
|
1157
1166
|
}
|
|
@@ -1184,22 +1193,25 @@ export class EntityManager {
|
|
|
1184
1193
|
validateParams(row, 'insert data');
|
|
1185
1194
|
allData.push(row);
|
|
1186
1195
|
allWhere.push(where);
|
|
1196
|
+
dataIndexes.push(i);
|
|
1187
1197
|
}
|
|
1188
|
-
if (
|
|
1189
|
-
return [...
|
|
1198
|
+
if (allData.length === 0) {
|
|
1199
|
+
return [...new Set(result)];
|
|
1190
1200
|
}
|
|
1191
1201
|
// `onCreate` generated values are for the insert clause only, they must not overwrite existing rows
|
|
1192
1202
|
if (generatedFields.size > 0 && !options.onConflictMergeFields) {
|
|
1193
1203
|
options.onConflictExcludeFields = [...(options.onConflictExcludeFields ?? []), ...generatedFields];
|
|
1194
1204
|
}
|
|
1195
1205
|
if (em.eventManager.hasListeners(EventType.beforeUpsert, meta)) {
|
|
1196
|
-
for (const
|
|
1197
|
-
const entity =
|
|
1206
|
+
for (const index of dataIndexes) {
|
|
1207
|
+
const entity = data[index];
|
|
1198
1208
|
await em.eventManager.dispatchEvent(EventType.beforeUpsert, { entity, em, meta }, meta);
|
|
1199
1209
|
}
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1210
|
+
dataIndexes.forEach((index, idx) => {
|
|
1211
|
+
if (Utils.isEntity(data[index])) {
|
|
1212
|
+
allData[idx] = em.#comparator.prepareEntity(data[index]);
|
|
1213
|
+
}
|
|
1214
|
+
});
|
|
1203
1215
|
}
|
|
1204
1216
|
const res = await em.withSessionContext(options.ctx ?? em.#transactionContext, ctx => em.driver.nativeUpdateMany(entityName, allWhere, allData, {
|
|
1205
1217
|
upsert: true,
|
|
@@ -1207,15 +1219,16 @@ export class EntityManager {
|
|
|
1207
1219
|
...options,
|
|
1208
1220
|
ctx,
|
|
1209
1221
|
}));
|
|
1210
|
-
entities.clear();
|
|
1211
|
-
entitiesByData.clear();
|
|
1212
1222
|
const loadPK = new Map();
|
|
1213
1223
|
allData.forEach((row, i) => {
|
|
1214
|
-
|
|
1224
|
+
// Managed inputs skipped by the SQL batch still occupy positions in `data`.
|
|
1225
|
+
const index = dataIndexes[i];
|
|
1226
|
+
const item = data[index];
|
|
1227
|
+
em.#unitOfWork.getChangeSetPersister().mapReturnedValues(Utils.isEntity(item) ? item : null, Utils.isEntity(item) ? {} : item,
|
|
1215
1228
|
// A suppressed conflict omits its row; positional mapping would hydrate a different entity.
|
|
1216
1229
|
res.rows?.length === allData.length ? res.rows[i] : undefined, meta, true);
|
|
1217
|
-
const entity = Utils.isEntity(
|
|
1218
|
-
?
|
|
1230
|
+
const entity = Utils.isEntity(item)
|
|
1231
|
+
? item
|
|
1219
1232
|
: em.#entityFactory.create(entityName, row, {
|
|
1220
1233
|
refresh: true,
|
|
1221
1234
|
initialized: true,
|
|
@@ -1226,18 +1239,19 @@ export class EntityManager {
|
|
|
1226
1239
|
}
|
|
1227
1240
|
entities.set(entity, row);
|
|
1228
1241
|
entitiesByData.set(row, entity);
|
|
1242
|
+
result[index] = entity;
|
|
1229
1243
|
});
|
|
1230
1244
|
// skip the reload only when RETURNING brought back a row for every input
|
|
1231
1245
|
// (onConflictWhere can suppress writes, leaving some rows out)
|
|
1232
1246
|
// oxfmt-ignore
|
|
1233
1247
|
const uniqueFields = options.onConflictFields ?? (Utils.isPlainObject(allWhere[0]) ? Object.keys(allWhere[0]).flatMap(key => Utils.splitPrimaryKeys(key)) : meta.primaryKeys);
|
|
1234
1248
|
const platform = this.getPlatform();
|
|
1235
|
-
const returning = getOnConflictReturningFields(meta, data[0], uniqueFields, options, platform);
|
|
1249
|
+
const returning = getOnConflictReturningFields(meta, data[dataIndexes[0]], uniqueFields, options, platform);
|
|
1236
1250
|
if (options.onConflictWhere && res.rows?.length !== allData.length) {
|
|
1237
1251
|
returning.push(...meta.comparableProps.filter(p => !p.lazy && !p.embeddable).map(p => p.name));
|
|
1238
1252
|
}
|
|
1239
1253
|
const reloadFields = returning.length > 0 &&
|
|
1240
|
-
!((platform.usesReturningStatement() || platform.usesOutputStatement()) && res.rows?.length ===
|
|
1254
|
+
!((platform.usesReturningStatement() || platform.usesOutputStatement()) && res.rows?.length === allData.length);
|
|
1241
1255
|
if (options.onConflictAction === 'ignore' || (!res.rows?.length && loadPK.size > 0) || reloadFields) {
|
|
1242
1256
|
const unique = meta.hydrateProps.filter(p => !p.lazy).map(p => p.name);
|
|
1243
1257
|
const add = new Set(propIndex !== false && propIndex >= 0
|
|
@@ -1247,7 +1261,8 @@ export class EntityManager {
|
|
|
1247
1261
|
Utils.keys(cond).forEach(key => add.add(key));
|
|
1248
1262
|
}
|
|
1249
1263
|
const where = { $or: [] };
|
|
1250
|
-
|
|
1264
|
+
dataIndexes.forEach((index, idx) => {
|
|
1265
|
+
const item = data[index];
|
|
1251
1266
|
where.$or[idx] = {};
|
|
1252
1267
|
const props = Array.isArray(uniqueFields) ? uniqueFields : Object.keys(item);
|
|
1253
1268
|
props.forEach(prop => {
|
|
@@ -1317,7 +1332,7 @@ export class EntityManager {
|
|
|
1317
1332
|
await em.eventManager.dispatchEvent(EventType.afterUpsert, { entity, em, meta }, meta);
|
|
1318
1333
|
}
|
|
1319
1334
|
}
|
|
1320
|
-
return [...
|
|
1335
|
+
return [...new Set(result)];
|
|
1321
1336
|
}
|
|
1322
1337
|
/**
|
|
1323
1338
|
* Runs your callback wrapped inside a database transaction.
|
|
@@ -1974,6 +1989,13 @@ export class EntityManager {
|
|
|
1974
1989
|
isInTransaction() {
|
|
1975
1990
|
return !!this.getContext(false).#transactionContext;
|
|
1976
1991
|
}
|
|
1992
|
+
/**
|
|
1993
|
+
* Checks whether transactions are disabled for this EntityManager.
|
|
1994
|
+
* @internal
|
|
1995
|
+
*/
|
|
1996
|
+
isTransactionDisabled() {
|
|
1997
|
+
return !!this.getContext(false).#disableTransactions;
|
|
1998
|
+
}
|
|
1977
1999
|
/**
|
|
1978
2000
|
* Gets the transaction context (driver dependent object used to make sure queries are executed on same connection).
|
|
1979
2001
|
*/
|
|
@@ -2329,7 +2351,7 @@ export class EntityManager {
|
|
|
2329
2351
|
/**
|
|
2330
2352
|
* @internal
|
|
2331
2353
|
*/
|
|
2332
|
-
async tryCache(entityName, config, key, refresh, merge) {
|
|
2354
|
+
async tryCache(entityName, config, key, refresh, merge, schema) {
|
|
2333
2355
|
config ??= this.config.get('resultCache').global;
|
|
2334
2356
|
if (!config) {
|
|
2335
2357
|
return undefined;
|
|
@@ -2351,6 +2373,7 @@ export class EntityManager {
|
|
|
2351
2373
|
merge: true,
|
|
2352
2374
|
convertCustomTypes: false,
|
|
2353
2375
|
refresh,
|
|
2376
|
+
schema,
|
|
2354
2377
|
recomputeSnapshot: true,
|
|
2355
2378
|
};
|
|
2356
2379
|
if (Array.isArray(cached) && merge) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mikro-orm/core",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.3.0-dev.0",
|
|
4
4
|
"description": "TypeScript ORM for Node.js based on Data Mapper, Unit of Work and Identity Map patterns. Supports MongoDB, MySQL, PostgreSQL and SQLite databases as well as usage with vanilla JavaScript.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"data-mapper",
|
|
@@ -207,7 +207,7 @@ export class ChangeSetPersister {
|
|
|
207
207
|
const keys = meta.getOwnConcurrencyCheckKeys();
|
|
208
208
|
for (const key of keys) {
|
|
209
209
|
cond[key] = changeSet.originalEntity[key];
|
|
210
|
-
if (changeSet.payload
|
|
210
|
+
if (Object.hasOwn(changeSet.payload, key)) {
|
|
211
211
|
tmp.push(key);
|
|
212
212
|
}
|
|
213
213
|
}
|
|
@@ -322,17 +322,19 @@ export class ChangeSetPersister {
|
|
|
322
322
|
}
|
|
323
323
|
// skip entity references as they don't have version values loaded
|
|
324
324
|
changeSets = changeSets.filter(cs => helper(cs.entity).__initialized);
|
|
325
|
-
const
|
|
325
|
+
const fields = meta.primaryKeys.concat(...concurrencyCheckKeys);
|
|
326
326
|
const $or = changeSets.map(cs => {
|
|
327
|
-
const cond = Utils.getPrimaryKeyCond(cs.originalEntity, primaryKeys);
|
|
327
|
+
const cond = Utils.getPrimaryKeyCond(cs.originalEntity, meta.primaryKeys);
|
|
328
|
+
for (const key of concurrencyCheckKeys) {
|
|
329
|
+
cond[key] = cs.originalEntity[key];
|
|
330
|
+
}
|
|
328
331
|
if (meta.ownsVersionProperty()) {
|
|
329
|
-
// @ts-ignore
|
|
330
332
|
cond[meta.versionProperty] = this.#platform.convertVersionValue(cs.entity[meta.versionProperty], meta.properties[meta.versionProperty]);
|
|
331
333
|
}
|
|
332
334
|
return cond;
|
|
333
335
|
});
|
|
334
336
|
options = this.prepareOptions(meta, options, {
|
|
335
|
-
fields
|
|
337
|
+
fields,
|
|
336
338
|
orderBy: meta.primaryKeys.reduce((o, pk) => {
|
|
337
339
|
o[pk] = 'asc';
|
|
338
340
|
return o;
|
|
@@ -344,8 +346,8 @@ export class ChangeSetPersister {
|
|
|
344
346
|
if (res.length !== changeSets.length) {
|
|
345
347
|
// a FK pointing to a composite PK is an array, so the values need to be compared deeply
|
|
346
348
|
const compare = (a, b, keys) => keys.every(k => equals(a[k], b[k]));
|
|
347
|
-
const entity = changeSets.find(
|
|
348
|
-
return !res.some(row => compare(
|
|
349
|
+
const entity = changeSets.find((_, index) => {
|
|
350
|
+
return !res.some(row => compare($or[index], row, meta.primaryKeys));
|
|
349
351
|
}).entity;
|
|
350
352
|
throw OptimisticLockError.lockFailed(entity);
|
|
351
353
|
}
|
|
@@ -467,7 +467,10 @@ export class UnitOfWork {
|
|
|
467
467
|
}
|
|
468
468
|
const groups = this.getChangeSetGroups();
|
|
469
469
|
const platform = this.#em.getPlatform();
|
|
470
|
-
const runInTransaction = !this.#em.isInTransaction() &&
|
|
470
|
+
const runInTransaction = !this.#em.isInTransaction() &&
|
|
471
|
+
!this.#em.isTransactionDisabled() &&
|
|
472
|
+
platform.supportsTransactions() &&
|
|
473
|
+
this.#em.config.get('implicitTransactions');
|
|
471
474
|
if (runInTransaction) {
|
|
472
475
|
const loggerContext = Utils.merge({ id: this.#em._id }, this.#em.getLoggerContext({ disableContextResolution: true }));
|
|
473
476
|
await this.#em.getConnection('write').transactional(trx => this.persistToDatabase(groups, trx), {
|
package/utils/QueryHelper.js
CHANGED
|
@@ -111,9 +111,17 @@ export class QueryHelper {
|
|
|
111
111
|
const op = this.liftGroupOperators(value, prop.targetMeta, metadata, k);
|
|
112
112
|
if (op) {
|
|
113
113
|
delete where[k];
|
|
114
|
-
|
|
114
|
+
const group = value[op].map((v) => {
|
|
115
115
|
return { [k]: v };
|
|
116
116
|
});
|
|
117
|
+
if (op in where) {
|
|
118
|
+
// Sibling groups are conjunctive, even when both use `$or`.
|
|
119
|
+
where.$and ??= [];
|
|
120
|
+
where.$and.push({ [op]: group });
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
where[op] = group;
|
|
124
|
+
}
|
|
117
125
|
}
|
|
118
126
|
}
|
|
119
127
|
return undefined;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { EntityManager } from '../EntityManager.js';
|
|
2
2
|
/** Uses `AsyncLocalStorage` to maintain a transaction-scoped EntityManager context across async operations. */
|
|
3
3
|
export declare class TransactionContext {
|
|
4
|
+
#private;
|
|
4
5
|
readonly em: EntityManager;
|
|
5
6
|
private static storage;
|
|
6
7
|
readonly id: number;
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
var _a;
|
|
1
2
|
import { createAsyncContext } from './AsyncContext.js';
|
|
2
3
|
/** Uses `AsyncLocalStorage` to maintain a transaction-scoped EntityManager context across async operations. */
|
|
3
4
|
export class TransactionContext {
|
|
4
5
|
em;
|
|
5
6
|
static storage = createAsyncContext();
|
|
7
|
+
#parent = _a.currentTransactionContext();
|
|
6
8
|
id;
|
|
7
9
|
constructor(em) {
|
|
8
10
|
this.em = em;
|
|
@@ -12,7 +14,7 @@ export class TransactionContext {
|
|
|
12
14
|
* Creates new TransactionContext instance and runs the code inside its domain.
|
|
13
15
|
*/
|
|
14
16
|
static create(em, next) {
|
|
15
|
-
const context = new
|
|
17
|
+
const context = new _a(em);
|
|
16
18
|
return this.storage.run(context, next);
|
|
17
19
|
}
|
|
18
20
|
/**
|
|
@@ -25,7 +27,11 @@ export class TransactionContext {
|
|
|
25
27
|
* Returns current EntityManager (if available).
|
|
26
28
|
*/
|
|
27
29
|
static getEntityManager(name = 'default') {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
+
let context = _a.currentTransactionContext();
|
|
31
|
+
while (context && context.em.name !== name) {
|
|
32
|
+
context = context.#parent;
|
|
33
|
+
}
|
|
34
|
+
return context?.em;
|
|
30
35
|
}
|
|
31
36
|
}
|
|
37
|
+
_a = TransactionContext;
|
|
@@ -81,10 +81,10 @@ export class TransactionManager {
|
|
|
81
81
|
*/
|
|
82
82
|
async executeWithoutTransaction(em, cb, options) {
|
|
83
83
|
const suspended = this.suspendTransaction(em);
|
|
84
|
-
const fork = this.createFork(em,
|
|
84
|
+
const fork = this.createFork(em, options);
|
|
85
85
|
const propagateToUpperContext = this.shouldPropagateToUpperContext(em);
|
|
86
86
|
try {
|
|
87
|
-
return await this.executeTransactionFlow(fork, cb, propagateToUpperContext, em);
|
|
87
|
+
return await TransactionContext.create(fork, () => this.executeTransactionFlow(fork, cb, propagateToUpperContext, em));
|
|
88
88
|
}
|
|
89
89
|
finally {
|
|
90
90
|
this.resumeTransaction(em, suspended);
|
package/utils/Utils.js
CHANGED
|
@@ -153,7 +153,7 @@ export function parseJsonSafe(value) {
|
|
|
153
153
|
/** Collection of general-purpose utility methods used throughout the ORM. */
|
|
154
154
|
export class Utils {
|
|
155
155
|
static PK_SEPARATOR = '~~~';
|
|
156
|
-
static #ORM_VERSION = '7.
|
|
156
|
+
static #ORM_VERSION = '7.3.0-dev.0';
|
|
157
157
|
/** Default session variable name backing an RLS filter argument (`current_setting('mikro.<filter>.<arg>')`). */
|
|
158
158
|
static getRlsSettingName(filterName, argName) {
|
|
159
159
|
return `mikro.${filterName}.${argName}`;
|