@mikro-orm/core 7.2.2-dev.2 → 7.2.2-dev.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/EntityManager.d.ts +6 -1
- package/EntityManager.js +88 -31
- package/entity/defineEntity.d.ts +1 -1
- package/entity/defineEntity.js +1 -1
- package/metadata/MetadataDiscovery.d.ts +2 -0
- package/metadata/MetadataDiscovery.js +11 -0
- package/metadata/types.d.ts +1 -1
- 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 +1 -1
- package/utils/Utils.js +1 -1
- package/utils/upsert-utils.d.ts +2 -1
- package/utils/upsert-utils.js +9 -5
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, {
|
|
@@ -994,10 +994,14 @@ export class EntityManager {
|
|
|
994
994
|
});
|
|
995
995
|
const uniqueFields = options.onConflictFields ??
|
|
996
996
|
(Utils.isPlainObject(where) ? Object.keys(where) : meta.primaryKeys);
|
|
997
|
-
const
|
|
997
|
+
const platform = this.getPlatform();
|
|
998
|
+
const returning = getOnConflictReturningFields(meta, data, uniqueFields, options, platform);
|
|
999
|
+
if (options.onConflictWhere && !ret.row) {
|
|
1000
|
+
returning.push(...Object.keys(data));
|
|
1001
|
+
}
|
|
998
1002
|
if (options.onConflictAction === 'ignore' ||
|
|
999
1003
|
!helper(entity).hasPrimaryKey() ||
|
|
1000
|
-
(returning.length > 0 && !(
|
|
1004
|
+
(returning.length > 0 && !((platform.usesReturningStatement() || platform.usesOutputStatement()) && ret.row))) {
|
|
1001
1005
|
const where = {};
|
|
1002
1006
|
if (Array.isArray(uniqueFields)) {
|
|
1003
1007
|
for (const prop of uniqueFields) {
|
|
@@ -1088,6 +1092,41 @@ export class EntityManager {
|
|
|
1088
1092
|
}
|
|
1089
1093
|
return ret;
|
|
1090
1094
|
}
|
|
1095
|
+
// merge fields are derived from the first row, rows of a different shape need their own statement
|
|
1096
|
+
if (!options.onConflictMergeFields && options.onConflictAction !== 'ignore') {
|
|
1097
|
+
const pks = this.metadata.get(entityName).primaryKeys;
|
|
1098
|
+
const shapes = new Map();
|
|
1099
|
+
data.forEach((row, i) => {
|
|
1100
|
+
// entity instances carry their unset properties as `undefined` keys, while in plain rows those are merged as `null`
|
|
1101
|
+
const shape = Object.keys(row)
|
|
1102
|
+
.filter(key => !pks.includes(key) && !(Utils.isEntity(row) && row[key] === undefined))
|
|
1103
|
+
.sort()
|
|
1104
|
+
.join();
|
|
1105
|
+
const idx = shapes.get(shape) ?? [];
|
|
1106
|
+
shapes.set(shape, idx);
|
|
1107
|
+
idx.push(i);
|
|
1108
|
+
});
|
|
1109
|
+
if (shapes.size > 1) {
|
|
1110
|
+
const ret = [];
|
|
1111
|
+
for (const idx of shapes.values()) {
|
|
1112
|
+
const res = await this.upsertMany(entityName, idx.map(i => data[i]), options);
|
|
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
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
// rows resolving to the same entity are returned once, as in the single statement path
|
|
1127
|
+
return [...new Set(ret.filter(entity => entity))];
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1091
1130
|
// the rows are replaced with their processed copies below, keep the caller's array intact
|
|
1092
1131
|
data = [...data];
|
|
1093
1132
|
const meta = this.metadata.get(entityName);
|
|
@@ -1095,9 +1134,10 @@ export class EntityManager {
|
|
|
1095
1134
|
const generatedFields = new Set();
|
|
1096
1135
|
const allData = [];
|
|
1097
1136
|
const allWhere = [];
|
|
1137
|
+
const dataIndexes = [];
|
|
1138
|
+
const result = [];
|
|
1098
1139
|
const entities = new Map();
|
|
1099
1140
|
const entitiesByData = new Map();
|
|
1100
|
-
const entitiesByAllDataIdx = new Map();
|
|
1101
1141
|
for (let i = 0; i < data.length; i++) {
|
|
1102
1142
|
let row = data[i];
|
|
1103
1143
|
let where;
|
|
@@ -1105,14 +1145,12 @@ export class EntityManager {
|
|
|
1105
1145
|
const entity = row;
|
|
1106
1146
|
if (helper(entity).__managed && helper(entity).__em === em && !this.config.get('upsertManaged')) {
|
|
1107
1147
|
em.#entityFactory.mergeData(meta, entity, row, { initialized: true });
|
|
1108
|
-
|
|
1109
|
-
entitiesByData.set(row, entity);
|
|
1148
|
+
result[i] = entity;
|
|
1110
1149
|
continue;
|
|
1111
1150
|
}
|
|
1112
1151
|
where = helper(entity).getPrimaryKey();
|
|
1113
1152
|
getOnCreateGeneratedFields(meta, entity).forEach(field => generatedFields.add(field));
|
|
1114
1153
|
em.#entityFactory.assignDefaultValues(entity, meta);
|
|
1115
|
-
entitiesByAllDataIdx.set(allData.length, entity);
|
|
1116
1154
|
row = em.#comparator.prepareEntity(entity);
|
|
1117
1155
|
}
|
|
1118
1156
|
else {
|
|
@@ -1122,8 +1160,7 @@ export class EntityManager {
|
|
|
1122
1160
|
const exists = em.#unitOfWork.getById(entityName, where, options.schema);
|
|
1123
1161
|
if (exists) {
|
|
1124
1162
|
em.assign(exists, row);
|
|
1125
|
-
|
|
1126
|
-
entitiesByData.set(row, exists);
|
|
1163
|
+
result[i] = exists;
|
|
1127
1164
|
continue;
|
|
1128
1165
|
}
|
|
1129
1166
|
}
|
|
@@ -1156,22 +1193,25 @@ export class EntityManager {
|
|
|
1156
1193
|
validateParams(row, 'insert data');
|
|
1157
1194
|
allData.push(row);
|
|
1158
1195
|
allWhere.push(where);
|
|
1196
|
+
dataIndexes.push(i);
|
|
1159
1197
|
}
|
|
1160
|
-
if (
|
|
1161
|
-
return [...
|
|
1198
|
+
if (allData.length === 0) {
|
|
1199
|
+
return [...new Set(result)];
|
|
1162
1200
|
}
|
|
1163
1201
|
// `onCreate` generated values are for the insert clause only, they must not overwrite existing rows
|
|
1164
1202
|
if (generatedFields.size > 0 && !options.onConflictMergeFields) {
|
|
1165
1203
|
options.onConflictExcludeFields = [...(options.onConflictExcludeFields ?? []), ...generatedFields];
|
|
1166
1204
|
}
|
|
1167
1205
|
if (em.eventManager.hasListeners(EventType.beforeUpsert, meta)) {
|
|
1168
|
-
for (const
|
|
1169
|
-
const entity =
|
|
1206
|
+
for (const index of dataIndexes) {
|
|
1207
|
+
const entity = data[index];
|
|
1170
1208
|
await em.eventManager.dispatchEvent(EventType.beforeUpsert, { entity, em, meta }, meta);
|
|
1171
1209
|
}
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1210
|
+
dataIndexes.forEach((index, idx) => {
|
|
1211
|
+
if (Utils.isEntity(data[index])) {
|
|
1212
|
+
allData[idx] = em.#comparator.prepareEntity(data[index]);
|
|
1213
|
+
}
|
|
1214
|
+
});
|
|
1175
1215
|
}
|
|
1176
1216
|
const res = await em.withSessionContext(options.ctx ?? em.#transactionContext, ctx => em.driver.nativeUpdateMany(entityName, allWhere, allData, {
|
|
1177
1217
|
upsert: true,
|
|
@@ -1179,15 +1219,16 @@ export class EntityManager {
|
|
|
1179
1219
|
...options,
|
|
1180
1220
|
ctx,
|
|
1181
1221
|
}));
|
|
1182
|
-
entities.clear();
|
|
1183
|
-
entitiesByData.clear();
|
|
1184
1222
|
const loadPK = new Map();
|
|
1185
1223
|
allData.forEach((row, i) => {
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
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,
|
|
1228
|
+
// A suppressed conflict omits its row; positional mapping would hydrate a different entity.
|
|
1229
|
+
res.rows?.length === allData.length ? res.rows[i] : undefined, meta, true);
|
|
1230
|
+
const entity = Utils.isEntity(item)
|
|
1231
|
+
? item
|
|
1191
1232
|
: em.#entityFactory.create(entityName, row, {
|
|
1192
1233
|
refresh: true,
|
|
1193
1234
|
initialized: true,
|
|
@@ -1198,13 +1239,19 @@ export class EntityManager {
|
|
|
1198
1239
|
}
|
|
1199
1240
|
entities.set(entity, row);
|
|
1200
1241
|
entitiesByData.set(row, entity);
|
|
1242
|
+
result[index] = entity;
|
|
1201
1243
|
});
|
|
1202
1244
|
// skip the reload only when RETURNING brought back a row for every input
|
|
1203
1245
|
// (onConflictWhere can suppress writes, leaving some rows out)
|
|
1204
1246
|
// oxfmt-ignore
|
|
1205
1247
|
const uniqueFields = options.onConflictFields ?? (Utils.isPlainObject(allWhere[0]) ? Object.keys(allWhere[0]).flatMap(key => Utils.splitPrimaryKeys(key)) : meta.primaryKeys);
|
|
1206
|
-
const
|
|
1207
|
-
const
|
|
1248
|
+
const platform = this.getPlatform();
|
|
1249
|
+
const returning = getOnConflictReturningFields(meta, data[dataIndexes[0]], uniqueFields, options, platform);
|
|
1250
|
+
if (options.onConflictWhere && res.rows?.length !== allData.length) {
|
|
1251
|
+
returning.push(...meta.comparableProps.filter(p => !p.lazy && !p.embeddable).map(p => p.name));
|
|
1252
|
+
}
|
|
1253
|
+
const reloadFields = returning.length > 0 &&
|
|
1254
|
+
!((platform.usesReturningStatement() || platform.usesOutputStatement()) && res.rows?.length === allData.length);
|
|
1208
1255
|
if (options.onConflictAction === 'ignore' || (!res.rows?.length && loadPK.size > 0) || reloadFields) {
|
|
1209
1256
|
const unique = meta.hydrateProps.filter(p => !p.lazy).map(p => p.name);
|
|
1210
1257
|
const add = new Set(propIndex !== false && propIndex >= 0
|
|
@@ -1214,7 +1261,8 @@ export class EntityManager {
|
|
|
1214
1261
|
Utils.keys(cond).forEach(key => add.add(key));
|
|
1215
1262
|
}
|
|
1216
1263
|
const where = { $or: [] };
|
|
1217
|
-
|
|
1264
|
+
dataIndexes.forEach((index, idx) => {
|
|
1265
|
+
const item = data[index];
|
|
1218
1266
|
where.$or[idx] = {};
|
|
1219
1267
|
const props = Array.isArray(uniqueFields) ? uniqueFields : Object.keys(item);
|
|
1220
1268
|
props.forEach(prop => {
|
|
@@ -1235,8 +1283,9 @@ export class EntityManager {
|
|
|
1235
1283
|
const row = data2.find(row => {
|
|
1236
1284
|
const tmp = {};
|
|
1237
1285
|
add.forEach(k => {
|
|
1238
|
-
|
|
1239
|
-
|
|
1286
|
+
const prop = meta.properties[k];
|
|
1287
|
+
if (!prop?.primary) {
|
|
1288
|
+
tmp[k] = prop?.customType ? prop.customType.convertToDatabaseValue(row[k], this.getPlatform()) : row[k];
|
|
1240
1289
|
}
|
|
1241
1290
|
});
|
|
1242
1291
|
return this.#comparator.matching(entityName, cond, tmp);
|
|
@@ -1283,7 +1332,7 @@ export class EntityManager {
|
|
|
1283
1332
|
await em.eventManager.dispatchEvent(EventType.afterUpsert, { entity, em, meta }, meta);
|
|
1284
1333
|
}
|
|
1285
1334
|
}
|
|
1286
|
-
return [...
|
|
1335
|
+
return [...new Set(result)];
|
|
1287
1336
|
}
|
|
1288
1337
|
/**
|
|
1289
1338
|
* Runs your callback wrapped inside a database transaction.
|
|
@@ -1940,6 +1989,13 @@ export class EntityManager {
|
|
|
1940
1989
|
isInTransaction() {
|
|
1941
1990
|
return !!this.getContext(false).#transactionContext;
|
|
1942
1991
|
}
|
|
1992
|
+
/**
|
|
1993
|
+
* Checks whether transactions are disabled for this EntityManager.
|
|
1994
|
+
* @internal
|
|
1995
|
+
*/
|
|
1996
|
+
isTransactionDisabled() {
|
|
1997
|
+
return !!this.getContext(false).#disableTransactions;
|
|
1998
|
+
}
|
|
1943
1999
|
/**
|
|
1944
2000
|
* Gets the transaction context (driver dependent object used to make sure queries are executed on same connection).
|
|
1945
2001
|
*/
|
|
@@ -2295,7 +2351,7 @@ export class EntityManager {
|
|
|
2295
2351
|
/**
|
|
2296
2352
|
* @internal
|
|
2297
2353
|
*/
|
|
2298
|
-
async tryCache(entityName, config, key, refresh, merge) {
|
|
2354
|
+
async tryCache(entityName, config, key, refresh, merge, schema) {
|
|
2299
2355
|
config ??= this.config.get('resultCache').global;
|
|
2300
2356
|
if (!config) {
|
|
2301
2357
|
return undefined;
|
|
@@ -2317,6 +2373,7 @@ export class EntityManager {
|
|
|
2317
2373
|
merge: true,
|
|
2318
2374
|
convertCustomTypes: false,
|
|
2319
2375
|
refresh,
|
|
2376
|
+
schema,
|
|
2320
2377
|
recomputeSnapshot: true,
|
|
2321
2378
|
};
|
|
2322
2379
|
if (Array.isArray(cached) && merge) {
|
package/entity/defineEntity.d.ts
CHANGED
|
@@ -267,7 +267,7 @@ export declare class UniversalPropertyOptionsBuilder<in out Value, in out Option
|
|
|
267
267
|
autoincrement: false;
|
|
268
268
|
}, IncludeKeys>, IncludeKeys>;
|
|
269
269
|
/**
|
|
270
|
-
* Add the property to the `returning`
|
|
270
|
+
* Add the property to the `returning` clause of inserts, updates and upserts, reloading the database value into the entity.
|
|
271
271
|
*/
|
|
272
272
|
returning(returning?: boolean): Pick<UniversalPropertyOptionsBuilder<Value, Options, IncludeKeys>, IncludeKeys>;
|
|
273
273
|
/**
|
package/entity/defineEntity.js
CHANGED
|
@@ -88,7 +88,7 @@ export class UniversalPropertyOptionsBuilder {
|
|
|
88
88
|
return this.assignOptions({ autoincrement });
|
|
89
89
|
}
|
|
90
90
|
/**
|
|
91
|
-
* Add the property to the `returning`
|
|
91
|
+
* Add the property to the `returning` clause of inserts, updates and upserts, reloading the database value into the entity.
|
|
92
92
|
*/
|
|
93
93
|
returning(returning = true) {
|
|
94
94
|
return this.assignOptions({ returning });
|
|
@@ -128,6 +128,8 @@ export declare class MetadataDiscovery {
|
|
|
128
128
|
private initRelation;
|
|
129
129
|
/** Resolves the `through` option of a virtual to-one relation into a read-only formula property. */
|
|
130
130
|
private initThroughRelation;
|
|
131
|
+
/** `returning` is only honoured for physical columns that can be hydrated back into the entity. */
|
|
132
|
+
private initReturning;
|
|
131
133
|
private initColumnType;
|
|
132
134
|
private getMappedType;
|
|
133
135
|
private getPrefix;
|
|
@@ -174,6 +174,7 @@ export class MetadataDiscovery {
|
|
|
174
174
|
this.initRelation(p);
|
|
175
175
|
this.initThroughRelation(m, p);
|
|
176
176
|
this.initColumnType(p);
|
|
177
|
+
this.initReturning(p);
|
|
177
178
|
});
|
|
178
179
|
forEachProp((m, p) => this.initIndexes(m, p));
|
|
179
180
|
filtered.forEach(meta => this.autoWireBidirectionalProperties(meta));
|
|
@@ -2129,6 +2130,16 @@ export class MetadataDiscovery {
|
|
|
2129
2130
|
prop.persist = false;
|
|
2130
2131
|
prop.formula = columns => this.#platform.getThroughRelationFormula(prop, columns);
|
|
2131
2132
|
}
|
|
2133
|
+
/** `returning` is only honoured for physical columns that can be hydrated back into the entity. */
|
|
2134
|
+
initReturning(prop) {
|
|
2135
|
+
const owning = prop.kind === ReferenceKind.SCALAR ||
|
|
2136
|
+
prop.kind === ReferenceKind.MANY_TO_ONE ||
|
|
2137
|
+
(prop.kind === ReferenceKind.ONE_TO_ONE && prop.owner) ||
|
|
2138
|
+
(prop.kind === ReferenceKind.EMBEDDED && prop.object);
|
|
2139
|
+
if (prop.returning && (!owning || prop.persist === false || prop.hydrate === false || prop.formula)) {
|
|
2140
|
+
prop.returning = false;
|
|
2141
|
+
}
|
|
2142
|
+
}
|
|
2132
2143
|
initColumnType(prop) {
|
|
2133
2144
|
this.initUnsigned(prop);
|
|
2134
2145
|
// Get the target properties for FK relations - use targetKey property if specified, otherwise PKs
|
package/metadata/types.d.ts
CHANGED
|
@@ -175,7 +175,7 @@ export interface PropertyOptions<Owner> {
|
|
|
175
175
|
*/
|
|
176
176
|
autoincrement?: boolean;
|
|
177
177
|
/**
|
|
178
|
-
* Add the property to the `returning`
|
|
178
|
+
* Add the property to the `returning` clause of inserts, updates and upserts, reloading the database value into the entity.
|
|
179
179
|
*/
|
|
180
180
|
returning?: boolean;
|
|
181
181
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mikro-orm/core",
|
|
3
|
-
"version": "7.2.2-dev.
|
|
3
|
+
"version": "7.2.2-dev.21",
|
|
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;
|
|
@@ -84,7 +84,7 @@ export class TransactionManager {
|
|
|
84
84
|
const fork = this.createFork(em, { ...options, disableTransactions: true });
|
|
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.2.2-dev.
|
|
156
|
+
static #ORM_VERSION = '7.2.2-dev.21';
|
|
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}`;
|
package/utils/upsert-utils.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { EntityData, EntityKey, EntityMetadata, FilterQuery } from '../typings.js';
|
|
2
2
|
import type { UpsertOptions } from '../drivers/IDatabaseDriver.js';
|
|
3
3
|
import { type Raw } from '../utils/RawQueryFragment.js';
|
|
4
|
+
import type { Platform } from '../platforms/Platform.js';
|
|
4
5
|
/** @internal */
|
|
5
6
|
export declare function getOnConflictFields<T>(meta: EntityMetadata<T> | undefined, data: EntityData<T>, uniqueFields: (keyof T)[] | Raw, options: UpsertOptions<T>): (keyof T)[];
|
|
6
7
|
/**
|
|
@@ -12,7 +13,7 @@ export declare function getOnConflictFields<T>(meta: EntityMetadata<T> | undefin
|
|
|
12
13
|
*/
|
|
13
14
|
export declare function getOnCreateGeneratedFields<T extends object>(meta: EntityMetadata<T>, data: T | EntityData<T>): EntityKey<T>[];
|
|
14
15
|
/** @internal */
|
|
15
|
-
export declare function getOnConflictReturningFields<T, P extends string>(meta: EntityMetadata<T> | undefined, data: EntityData<T>, uniqueFields: (keyof T)[] | Raw, options: UpsertOptions<T, P
|
|
16
|
+
export declare function getOnConflictReturningFields<T, P extends string>(meta: EntityMetadata<T> | undefined, data: EntityData<T>, uniqueFields: (keyof T)[] | Raw, options: UpsertOptions<T, P>, platform?: Platform): (keyof T)[] | '*';
|
|
16
17
|
/** @internal */
|
|
17
18
|
export declare function getWhereCondition<T extends object>(meta: EntityMetadata<T>, onConflictFields: (keyof T)[] | Raw | undefined, data: EntityData<T>, where: FilterQuery<T>): {
|
|
18
19
|
where: FilterQuery<T>;
|
package/utils/upsert-utils.js
CHANGED
|
@@ -89,11 +89,15 @@ export function getOnCreateGeneratedFields(meta, data) {
|
|
|
89
89
|
.map(prop => prop.name);
|
|
90
90
|
}
|
|
91
91
|
/** @internal */
|
|
92
|
-
export function getOnConflictReturningFields(meta, data, uniqueFields, options) {
|
|
92
|
+
export function getOnConflictReturningFields(meta, data, uniqueFields, options, platform) {
|
|
93
93
|
/* v8 ignore next */
|
|
94
94
|
if (!meta) {
|
|
95
95
|
return '*';
|
|
96
96
|
}
|
|
97
|
+
// props with explicit `returning` hint are reloaded even when supplied, as the database might normalize them
|
|
98
|
+
const returning = platform?.usesReturningStatement() || platform?.usesOutputStatement()
|
|
99
|
+
? meta.props.filter(p => p.returning).map(p => p.name)
|
|
100
|
+
: [];
|
|
97
101
|
const keys = meta.comparableProps
|
|
98
102
|
.filter(p => {
|
|
99
103
|
if (p.lazy || p.embeddable) {
|
|
@@ -112,17 +116,17 @@ export function getOnConflictReturningFields(meta, data, uniqueFields, options)
|
|
|
112
116
|
keys.push(meta.versionProperty);
|
|
113
117
|
}
|
|
114
118
|
if (options.onConflictAction === 'ignore') {
|
|
115
|
-
return keys;
|
|
119
|
+
return Utils.unique([...keys, ...returning]);
|
|
116
120
|
}
|
|
117
121
|
if (options.onConflictMergeFields) {
|
|
118
122
|
const onConflictMergeFields = expandFields(meta, options.onConflictMergeFields);
|
|
119
|
-
return keys.filter(key => !onConflictMergeFields.includes(key));
|
|
123
|
+
return Utils.unique([...keys.filter(key => !onConflictMergeFields.includes(key)), ...returning]);
|
|
120
124
|
}
|
|
121
125
|
if (options.onConflictExcludeFields) {
|
|
122
126
|
const onConflictExcludeFields = expandFields(meta, options.onConflictExcludeFields);
|
|
123
|
-
return [...
|
|
127
|
+
return Utils.unique([...keys, ...onConflictExcludeFields, ...returning]);
|
|
124
128
|
}
|
|
125
|
-
return keys.filter(key => !(key in data));
|
|
129
|
+
return Utils.unique([...keys.filter(key => !(key in data)), ...returning]);
|
|
126
130
|
}
|
|
127
131
|
function getPropertyValue(obj, key) {
|
|
128
132
|
if (!key.includes('.')) {
|