@mikro-orm/core 7.2.4-dev.14 → 7.2.4-dev.16
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.js +49 -14
- package/package.json +1 -1
- package/utils/Utils.js +1 -1
- package/utils/upsert-utils.js +2 -1
package/EntityManager.js
CHANGED
|
@@ -1025,6 +1025,15 @@ export class EntityManager {
|
|
|
1025
1025
|
where[meta.primaryKeys[0]] = ret.insertId;
|
|
1026
1026
|
}
|
|
1027
1027
|
}
|
|
1028
|
+
// a `null` unique value cannot identify the row, and as it never conflicts, the row was inserted
|
|
1029
|
+
if (!Utils.hasObjectKeys(where)) {
|
|
1030
|
+
const insertId = meta.simplePK && ret.insertId != null ? { [meta.primaryKeys[0]]: ret.insertId } : {};
|
|
1031
|
+
Object.assign(where, Utils.getPrimaryKeyCond(entity, meta.primaryKeys) ?? insertId);
|
|
1032
|
+
}
|
|
1033
|
+
// an empty condition would hydrate the entity from an arbitrary row, mongo still gets one for nested conflict fields
|
|
1034
|
+
if (!Utils.hasObjectKeys(where) && platform.usesPivotTable()) {
|
|
1035
|
+
throw new Error(`Cannot find the upserted ${meta.className} row, as neither its primary key nor a unique value is known`);
|
|
1036
|
+
}
|
|
1028
1037
|
const data2 = await em.withSessionContext(options.ctx ?? em.#transactionContext, ctx => this.driver.findOne(meta.class, where, {
|
|
1029
1038
|
fields: returning.concat(...(options.onConflictMergeFields ?? [])),
|
|
1030
1039
|
ctx,
|
|
@@ -1032,6 +1041,9 @@ export class EntityManager {
|
|
|
1032
1041
|
connectionType: 'write',
|
|
1033
1042
|
schema: options.schema,
|
|
1034
1043
|
}));
|
|
1044
|
+
if (!data2) {
|
|
1045
|
+
throw new Error(`Cannot find the upserted ${meta.className} row for condition ${JSON.stringify(where)}`);
|
|
1046
|
+
}
|
|
1035
1047
|
em.getHydrator().hydrate(entity, meta, data2, em.#entityFactory, 'full', false, true);
|
|
1036
1048
|
}
|
|
1037
1049
|
// recompute the data as there might be some values missing (e.g. those with db column defaults)
|
|
@@ -1183,8 +1195,10 @@ export class EntityManager {
|
|
|
1183
1195
|
unique.findIndex(p => data[p] ?? data[p.substring(0, p.indexOf('.'))] != null);
|
|
1184
1196
|
const tmp = getWhereCondition(meta, options.onConflictFields, row, where);
|
|
1185
1197
|
propIndex = tmp.propIndex;
|
|
1198
|
+
// a partial composite PK cannot identify the row
|
|
1199
|
+
const partialPK = meta.compositePK && !Utils.isPlainObject(tmp.where) && meta.primaryKeys.some(pk => row[pk] == null);
|
|
1186
1200
|
where = QueryHelper.processWhere({
|
|
1187
|
-
where: tmp.where,
|
|
1201
|
+
where: partialPK ? {} : tmp.where,
|
|
1188
1202
|
entityName,
|
|
1189
1203
|
metadata: this.metadata,
|
|
1190
1204
|
platform: this.getPlatform(),
|
|
@@ -1199,6 +1213,25 @@ export class EntityManager {
|
|
|
1199
1213
|
allWhere.push(where);
|
|
1200
1214
|
dataIndexes.push(i);
|
|
1201
1215
|
}
|
|
1216
|
+
// mongo inserts the rows with an empty condition as part of the batch and reports their ids
|
|
1217
|
+
const positional = !this.getPlatform().usesPivotTable() ||
|
|
1218
|
+
((this.getPlatform().usesReturningStatement() || this.getPlatform().usesOutputStatement()) &&
|
|
1219
|
+
options.onConflictAction !== 'ignore' &&
|
|
1220
|
+
!options.onConflictWhere);
|
|
1221
|
+
const hasUniqueKey = !!options.onConflictFields || meta.uniques.length > 0 || meta.props.some(p => p.unique && !p.primary);
|
|
1222
|
+
// only an autoincrement PK can be derived from the `insertId` of the batch
|
|
1223
|
+
const autoincrement = meta.getPrimaryProps().some(p => p.autoincrement);
|
|
1224
|
+
// a TPT child table does not return its rows, so they are never mapped by position
|
|
1225
|
+
const peel = !!meta.tptParent || (!positional && (hasUniqueKey || !autoincrement));
|
|
1226
|
+
// a row with a `null` unique value has nothing to be reloaded by, the single row path maps its PK from the insert
|
|
1227
|
+
for (let idx = 0; peel && idx < allWhere.length; idx++) {
|
|
1228
|
+
if (!Utils.hasObjectKeys(allWhere[idx])) {
|
|
1229
|
+
result[dataIndexes[idx]] = await em.upsert(entityName, data[dataIndexes[idx]], options);
|
|
1230
|
+
allData.splice(idx, 1);
|
|
1231
|
+
allWhere.splice(idx, 1);
|
|
1232
|
+
dataIndexes.splice(idx--, 1);
|
|
1233
|
+
}
|
|
1234
|
+
}
|
|
1202
1235
|
if (allData.length === 0) {
|
|
1203
1236
|
return [...new Set(result)];
|
|
1204
1237
|
}
|
|
@@ -1249,7 +1282,8 @@ export class EntityManager {
|
|
|
1249
1282
|
// (onConflictWhere can suppress writes, leaving some rows out)
|
|
1250
1283
|
// oxfmt-ignore
|
|
1251
1284
|
const getUniqueFields = (where) => options.onConflictFields ?? (Utils.isPlainObject(where) ? Object.keys(where).flatMap(key => Utils.splitPrimaryKeys(key)) : meta.primaryKeys);
|
|
1252
|
-
|
|
1285
|
+
// a row without a condition has no unique fields to derive
|
|
1286
|
+
const uniqueFields = getUniqueFields(allWhere.find(cond => Utils.hasObjectKeys(cond)) ?? allWhere[0]);
|
|
1253
1287
|
// an empty condition identifies no row, it must not turn into a reload condition matching every row
|
|
1254
1288
|
const getRowUniqueFields = (where) => Utils.isPlainObject(where) && !Utils.hasObjectKeys(where) ? uniqueFields : getUniqueFields(where);
|
|
1255
1289
|
const platform = this.getPlatform();
|
|
@@ -1268,11 +1302,13 @@ export class EntityManager {
|
|
|
1268
1302
|
Utils.keys(cond).forEach(key => add.add(key));
|
|
1269
1303
|
}
|
|
1270
1304
|
const where = { $or: [] };
|
|
1305
|
+
// a row without a condition was inserted, only the primary key reported for it can identify it
|
|
1306
|
+
const insertedPKs = allData.map((row, idx) => Utils.hasObjectKeys(allWhere[idx]) ? null : Utils.getPrimaryKeyCond(entitiesByData.get(row), meta.primaryKeys));
|
|
1271
1307
|
dataIndexes.forEach((index, idx) => {
|
|
1272
1308
|
const item = data[index];
|
|
1273
|
-
where.$or[idx] = {};
|
|
1309
|
+
where.$or[idx] = { ...insertedPKs[idx] };
|
|
1274
1310
|
// the inputs of one batch can be identified by different unique keys
|
|
1275
|
-
const fields = getRowUniqueFields(allWhere[idx]);
|
|
1311
|
+
const fields = insertedPKs[idx] ? [] : getRowUniqueFields(allWhere[idx]);
|
|
1276
1312
|
const props = Array.isArray(fields) ? fields : Object.keys(item);
|
|
1277
1313
|
props.forEach(prop => {
|
|
1278
1314
|
where.$or[idx][prop] = item[prop];
|
|
@@ -1299,10 +1335,8 @@ export class EntityManager {
|
|
|
1299
1335
|
});
|
|
1300
1336
|
return tmp;
|
|
1301
1337
|
});
|
|
1302
|
-
// rows carry the keys of every condition in the batch, compare only this one's
|
|
1303
|
-
const isMatch = (cond, idx) => this.#comparator.matching(entityName, cond,
|
|
1304
|
-
? Object.fromEntries(Object.entries(comparableRows[idx]).filter(([k]) => k in cond))
|
|
1305
|
-
: comparableRows[idx]);
|
|
1338
|
+
// rows carry the keys of every condition in the batch, compare only this one's
|
|
1339
|
+
const isMatch = (cond, idx) => this.#comparator.matching(entityName, cond, Object.fromEntries(Object.entries(comparableRows[idx]).filter(([k]) => k in cond)));
|
|
1306
1340
|
// When every condition key is a comparable prop checked with `!==`, a matching row has the same primitive
|
|
1307
1341
|
// values, so rows can be grouped by those values once instead of comparing every row for every entity.
|
|
1308
1342
|
// Props outside `comparableProps` are skipped by the comparator, so they never qualify for grouping.
|
|
@@ -1341,12 +1375,13 @@ export class EntityManager {
|
|
|
1341
1375
|
if (loadPK.size !== data2.length && Array.isArray(uniqueFields)) {
|
|
1342
1376
|
for (let i = 0; i < allData.length; i++) {
|
|
1343
1377
|
const data = allData[i];
|
|
1344
|
-
const fields = getRowUniqueFields(allWhere[i]);
|
|
1345
|
-
const cond =
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1378
|
+
const fields = (insertedPKs[i] ? meta.primaryKeys : getRowUniqueFields(allWhere[i]));
|
|
1379
|
+
const cond = insertedPKs[i] ??
|
|
1380
|
+
fields.reduce((a, b) => {
|
|
1381
|
+
// @ts-ignore
|
|
1382
|
+
a[b] = data[b];
|
|
1383
|
+
return a;
|
|
1384
|
+
}, {});
|
|
1350
1385
|
const entity = entitiesByData.get(data);
|
|
1351
1386
|
const row = data2.find(item => {
|
|
1352
1387
|
const pk = fields.reduce((a, b) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mikro-orm/core",
|
|
3
|
-
"version": "7.2.4-dev.
|
|
3
|
+
"version": "7.2.4-dev.16",
|
|
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",
|
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.4-dev.
|
|
156
|
+
static #ORM_VERSION = '7.2.4-dev.16';
|
|
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.js
CHANGED
|
@@ -103,7 +103,8 @@ export function getOnConflictReturningFields(meta, data, uniqueFields, options,
|
|
|
103
103
|
if (p.lazy || p.embeddable) {
|
|
104
104
|
return false;
|
|
105
105
|
}
|
|
106
|
-
|
|
106
|
+
// a PK missing in the data is generated by the database
|
|
107
|
+
if (p.autoincrement || (p.primary && data[p.name] == null)) {
|
|
107
108
|
return true;
|
|
108
109
|
}
|
|
109
110
|
// for a raw `onConflictFields` fragment we can't introspect which columns form the conflict
|