@mikro-orm/core 7.1.15-dev.9 → 7.1.16-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/hydration/ObjectHydrator.d.ts +2 -0
- package/hydration/ObjectHydrator.js +12 -8
- package/package.json +1 -1
- package/typings.d.ts +6 -0
- package/typings.js +19 -0
- package/unit-of-work/ChangeSetPersister.js +17 -13
- package/unit-of-work/UnitOfWork.js +6 -3
- package/utils/EntityComparator.d.ts +2 -0
- package/utils/EntityComparator.js +6 -2
- package/utils/Utils.js +1 -1
- package/utils/upsert-utils.js +10 -3
|
@@ -19,6 +19,8 @@ export declare class ObjectHydrator extends Hydrator {
|
|
|
19
19
|
getEntityHydrator<T extends object>(meta: EntityMetadata<T>, type: 'full' | 'reference', normalizeAccessors?: boolean): EntityHydrator<T>;
|
|
20
20
|
private createCollectionItemMapper;
|
|
21
21
|
private wrap;
|
|
22
|
+
/** Renders a key as a single-quoted JS string literal, safe to embed in generated code. */
|
|
23
|
+
private quote;
|
|
22
24
|
private safeKey;
|
|
23
25
|
}
|
|
24
26
|
export {};
|
|
@@ -87,7 +87,7 @@ export class ObjectHydrator extends Hydrator {
|
|
|
87
87
|
ret.push(` if (data${dataKey} === null) {`);
|
|
88
88
|
if (prop.ref) {
|
|
89
89
|
ret.push(` entity${entityKey} = new ScalarReference();`);
|
|
90
|
-
ret.push(` entity${entityKey}.bind(entity,
|
|
90
|
+
ret.push(` entity${entityKey}.bind(entity, ${this.quote(prop.name)});`);
|
|
91
91
|
ret.push(` entity${entityKey}.set(${nullVal});`);
|
|
92
92
|
}
|
|
93
93
|
else {
|
|
@@ -127,14 +127,14 @@ export class ObjectHydrator extends Hydrator {
|
|
|
127
127
|
if (prop.ref) {
|
|
128
128
|
ret.push(` const value = isScalarReference(entity${entityKey}) ? entity${entityKey}.unwrap() : entity${entityKey};`);
|
|
129
129
|
ret.push(` entity${entityKey} = oldValue_${idx} ?? new ScalarReference(value);`);
|
|
130
|
-
ret.push(` entity${entityKey}.bind(entity,
|
|
130
|
+
ret.push(` entity${entityKey}.bind(entity, ${this.quote(prop.name)});`);
|
|
131
131
|
ret.push(` entity${entityKey}.set(value);`);
|
|
132
132
|
}
|
|
133
133
|
ret.push(` }`);
|
|
134
134
|
if (prop.ref) {
|
|
135
135
|
ret.push(` if (!entity${entityKey}) {`);
|
|
136
136
|
ret.push(` entity${entityKey} = new ScalarReference();`);
|
|
137
|
-
ret.push(` entity${entityKey}.bind(entity,
|
|
137
|
+
ret.push(` entity${entityKey}.bind(entity, ${this.quote(prop.name)});`);
|
|
138
138
|
ret.push(` }`);
|
|
139
139
|
}
|
|
140
140
|
return ret;
|
|
@@ -220,7 +220,7 @@ export class ObjectHydrator extends Hydrator {
|
|
|
220
220
|
ret.push(` }`);
|
|
221
221
|
ret.push(` if (Array.isArray(data${dataKey})) {`);
|
|
222
222
|
ret.push(` const items = data${dataKey}.map(value => createCollectionItem_${this.safeKey(prop.name)}(value, entity));`);
|
|
223
|
-
ret.push(` const coll = Collection.create(entity,
|
|
223
|
+
ret.push(` const coll = Collection.create(entity, ${this.quote(prop.name)}, items, newEntity);`);
|
|
224
224
|
ret.push(` if (newEntity) {`);
|
|
225
225
|
ret.push(` coll.setDirty();`);
|
|
226
226
|
ret.push(` } else {`);
|
|
@@ -231,11 +231,11 @@ export class ObjectHydrator extends Hydrator {
|
|
|
231
231
|
if (!this.platform.usesPivotTable() && prop.owner && prop.kind === ReferenceKind.MANY_TO_MANY) {
|
|
232
232
|
ret.push(` } else if (!entity${entityKey} && Array.isArray(data${dataKey})) {`);
|
|
233
233
|
const items = this.platform.usesPivotTable() || !prop.owner ? 'undefined' : '[]';
|
|
234
|
-
ret.push(` const coll = Collection.create(entity,
|
|
234
|
+
ret.push(` const coll = Collection.create(entity, ${this.quote(prop.name)}, ${items}, !!data${dataKey} || newEntity);`);
|
|
235
235
|
ret.push(` coll.setDirty(false);`);
|
|
236
236
|
}
|
|
237
237
|
ret.push(` } else if (!entity${entityKey}) {`);
|
|
238
|
-
ret.push(` const coll = Collection.create(entity,
|
|
238
|
+
ret.push(` const coll = Collection.create(entity, ${this.quote(prop.name)}, undefined, newEntity);`);
|
|
239
239
|
ret.push(` coll.setDirty(false);`);
|
|
240
240
|
ret.push(` }`);
|
|
241
241
|
return ret;
|
|
@@ -431,10 +431,14 @@ export class ObjectHydrator extends Hydrator {
|
|
|
431
431
|
return lines;
|
|
432
432
|
}
|
|
433
433
|
wrap(key) {
|
|
434
|
-
if (/^\[
|
|
434
|
+
if (/^\[idx_\d+]$/.exec(key)) {
|
|
435
435
|
return key;
|
|
436
436
|
}
|
|
437
|
-
return /^\w+$/.exec(key) ? `.${key}` : `[
|
|
437
|
+
return /^\w+$/.exec(key) ? `.${key}` : `[${this.quote(key)}]`;
|
|
438
|
+
}
|
|
439
|
+
/** Renders a key as a single-quoted JS string literal, safe to embed in generated code. */
|
|
440
|
+
quote(key) {
|
|
441
|
+
return `'${key.replace(/\\/g, '\\\\').replace(/'/g, "\\'")}'`;
|
|
438
442
|
}
|
|
439
443
|
safeKey(key) {
|
|
440
444
|
return key.replace(/\W/g, '_');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mikro-orm/core",
|
|
3
|
-
"version": "7.1.
|
|
3
|
+
"version": "7.1.16-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",
|
package/typings.d.ts
CHANGED
|
@@ -1050,6 +1050,12 @@ export declare class EntityMetadata<Entity = any, Class extends EntityCtor<Entit
|
|
|
1050
1050
|
constructor(meta?: Partial<EntityMetadata>);
|
|
1051
1051
|
addProperty(prop: Partial<EntityProperty<Entity>>): void;
|
|
1052
1052
|
removeProperty(name: string, sync?: boolean): void;
|
|
1053
|
+
/** For TPT entities, the version column exists only on the table of the entity that declares it. */
|
|
1054
|
+
ownsVersionProperty(): boolean;
|
|
1055
|
+
/** For TPT entities, concurrency check columns exist only on the table of the entity that declares them. */
|
|
1056
|
+
getOwnConcurrencyCheckKeys(): EntityKey<Entity>[];
|
|
1057
|
+
/** Whether updates of this table are guarded by a version property or concurrency check columns it owns. */
|
|
1058
|
+
hasOptimisticLock(): boolean;
|
|
1053
1059
|
getPrimaryProps(flatten?: boolean): EntityProperty<Entity>[];
|
|
1054
1060
|
getPrimaryProp(): EntityProperty<Entity>;
|
|
1055
1061
|
/**
|
package/typings.js
CHANGED
|
@@ -79,6 +79,25 @@ export class EntityMetadata {
|
|
|
79
79
|
this.sync();
|
|
80
80
|
}
|
|
81
81
|
}
|
|
82
|
+
/** For TPT entities, the version column exists only on the table of the entity that declares it. */
|
|
83
|
+
ownsVersionProperty() {
|
|
84
|
+
if (!this.versionProperty) {
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
return this.inheritanceType !== 'tpt' || !this.ownProps || this.ownProps.some(p => p.name === this.versionProperty);
|
|
88
|
+
}
|
|
89
|
+
/** For TPT entities, concurrency check columns exist only on the table of the entity that declares them. */
|
|
90
|
+
getOwnConcurrencyCheckKeys() {
|
|
91
|
+
const keys = [...this.concurrencyCheckKeys];
|
|
92
|
+
if (this.inheritanceType !== 'tpt' || !this.ownProps) {
|
|
93
|
+
return keys;
|
|
94
|
+
}
|
|
95
|
+
return keys.filter(key => this.ownProps.some(p => p.name === key));
|
|
96
|
+
}
|
|
97
|
+
/** Whether updates of this table are guarded by a version property or concurrency check columns it owns. */
|
|
98
|
+
hasOptimisticLock() {
|
|
99
|
+
return this.ownsVersionProperty() || this.getOwnConcurrencyCheckKeys().length > 0;
|
|
100
|
+
}
|
|
82
101
|
getPrimaryProps(flatten = false) {
|
|
83
102
|
const pks = this.primaryKeys.map(pk => this.properties[pk]);
|
|
84
103
|
if (flatten) {
|
|
@@ -204,13 +204,14 @@ export class ChangeSetPersister {
|
|
|
204
204
|
}
|
|
205
205
|
checkConcurrencyKeys(meta, changeSet, cond) {
|
|
206
206
|
const tmp = [];
|
|
207
|
-
|
|
207
|
+
const keys = meta.getOwnConcurrencyCheckKeys();
|
|
208
|
+
for (const key of keys) {
|
|
208
209
|
cond[key] = changeSet.originalEntity[key];
|
|
209
210
|
if (changeSet.payload[key]) {
|
|
210
211
|
tmp.push(key);
|
|
211
212
|
}
|
|
212
213
|
}
|
|
213
|
-
if (tmp.length === 0 &&
|
|
214
|
+
if (tmp.length === 0 && keys.length > 0) {
|
|
214
215
|
throw OptimisticLockError.lockFailed(changeSet.entity);
|
|
215
216
|
}
|
|
216
217
|
}
|
|
@@ -303,32 +304,33 @@ export class ChangeSetPersister {
|
|
|
303
304
|
options = this.prepareOptions(meta, options, {
|
|
304
305
|
convertCustomTypes: false,
|
|
305
306
|
});
|
|
306
|
-
if (meta.
|
|
307
|
-
(!meta.
|
|
307
|
+
if (meta.getOwnConcurrencyCheckKeys().length === 0 &&
|
|
308
|
+
(!meta.ownsVersionProperty() || changeSet.entity[meta.versionProperty] == null)) {
|
|
308
309
|
return this.#driver.nativeUpdate(changeSet.meta.class, cond, changeSet.payload, options);
|
|
309
310
|
}
|
|
310
|
-
if (meta.
|
|
311
|
+
if (meta.ownsVersionProperty()) {
|
|
311
312
|
cond[meta.versionProperty] = this.#platform.convertVersionValue(changeSet.entity[meta.versionProperty], meta.properties[meta.versionProperty]);
|
|
312
313
|
}
|
|
313
314
|
this.checkConcurrencyKeys(meta, changeSet, cond);
|
|
314
315
|
return this.#driver.nativeUpdate(changeSet.meta.class, cond, changeSet.payload, options);
|
|
315
316
|
}
|
|
316
317
|
async checkOptimisticLocks(meta, changeSets, options) {
|
|
317
|
-
|
|
318
|
-
|
|
318
|
+
const concurrencyCheckKeys = meta.getOwnConcurrencyCheckKeys();
|
|
319
|
+
if (concurrencyCheckKeys.length === 0 &&
|
|
320
|
+
(!meta.ownsVersionProperty() || changeSets.every(cs => cs.entity[meta.versionProperty] == null))) {
|
|
319
321
|
return;
|
|
320
322
|
}
|
|
321
323
|
// skip entity references as they don't have version values loaded
|
|
322
324
|
changeSets = changeSets.filter(cs => helper(cs.entity).__initialized);
|
|
325
|
+
const primaryKeys = meta.primaryKeys.concat(...concurrencyCheckKeys);
|
|
323
326
|
const $or = changeSets.map(cs => {
|
|
324
|
-
const cond = Utils.getPrimaryKeyCond(cs.originalEntity,
|
|
325
|
-
if (meta.
|
|
327
|
+
const cond = Utils.getPrimaryKeyCond(cs.originalEntity, primaryKeys);
|
|
328
|
+
if (meta.ownsVersionProperty()) {
|
|
326
329
|
// @ts-ignore
|
|
327
330
|
cond[meta.versionProperty] = this.#platform.convertVersionValue(cs.entity[meta.versionProperty], meta.properties[meta.versionProperty]);
|
|
328
331
|
}
|
|
329
332
|
return cond;
|
|
330
333
|
});
|
|
331
|
-
const primaryKeys = meta.primaryKeys.concat(...meta.concurrencyCheckKeys);
|
|
332
334
|
options = this.prepareOptions(meta, options, {
|
|
333
335
|
fields: primaryKeys,
|
|
334
336
|
orderBy: meta.primaryKeys.reduce((o, pk) => {
|
|
@@ -336,7 +338,9 @@ export class ChangeSetPersister {
|
|
|
336
338
|
return o;
|
|
337
339
|
}, {}),
|
|
338
340
|
});
|
|
339
|
-
|
|
341
|
+
// TPT tables query their own metadata, as the version column might not live on the root table
|
|
342
|
+
const target = meta.inheritanceType === 'tpt' ? meta : meta.root;
|
|
343
|
+
const res = await this.#driver.find(target.class, { $or }, options);
|
|
340
344
|
if (res.length !== changeSets.length) {
|
|
341
345
|
// a FK pointing to a composite PK is an array, so the values need to be compared deeply
|
|
342
346
|
const compare = (a, b, keys) => keys.every(k => equals(a[k], b[k]));
|
|
@@ -347,7 +351,7 @@ export class ChangeSetPersister {
|
|
|
347
351
|
}
|
|
348
352
|
}
|
|
349
353
|
checkOptimisticLock(meta, changeSet, res) {
|
|
350
|
-
if ((meta.
|
|
354
|
+
if ((meta.ownsVersionProperty() || meta.getOwnConcurrencyCheckKeys().length > 0) && res && !res.affectedRows) {
|
|
351
355
|
throw OptimisticLockError.lockFailed(changeSet.entity);
|
|
352
356
|
}
|
|
353
357
|
}
|
|
@@ -356,7 +360,7 @@ export class ChangeSetPersister {
|
|
|
356
360
|
* so we use a single query in case of both versioning and default values is used.
|
|
357
361
|
*/
|
|
358
362
|
async reloadVersionValues(meta, changeSets, options) {
|
|
359
|
-
const reloadProps = meta.
|
|
363
|
+
const reloadProps = meta.ownsVersionProperty() && !this.#usesReturningStatement ? [meta.properties[meta.versionProperty]] : [];
|
|
360
364
|
if (changeSets[0].type === ChangeSetType.CREATE) {
|
|
361
365
|
for (const prop of meta.props) {
|
|
362
366
|
if (prop.persist === false) {
|
|
@@ -331,6 +331,7 @@ export class UnitOfWork {
|
|
|
331
331
|
let parentCs = changeSet.tptChangeSets.find(pc => pc.meta === current);
|
|
332
332
|
if (!parentCs) {
|
|
333
333
|
parentCs = new ChangeSet(entity, changeSet.type, {}, current);
|
|
334
|
+
parentCs.originalEntity = changeSet.originalEntity;
|
|
334
335
|
changeSet.tptChangeSets.splice(idx, 0, parentCs);
|
|
335
336
|
}
|
|
336
337
|
idx++;
|
|
@@ -705,13 +706,14 @@ export class UnitOfWork {
|
|
|
705
706
|
payload[pk] = identifiers[i] ?? originalChangeSet.payload[pk];
|
|
706
707
|
}
|
|
707
708
|
}
|
|
708
|
-
|
|
709
|
+
// the table declaring the version property or a concurrency check still needs its bump and lock check
|
|
710
|
+
if (!isCreate && Object.keys(payload).length === 0 && !current.hasOptimisticLock()) {
|
|
709
711
|
current = current.tptParent;
|
|
710
712
|
continue;
|
|
711
713
|
}
|
|
712
714
|
const cs = new ChangeSet(entity, originalChangeSet.type, payload, current);
|
|
715
|
+
cs.originalEntity = originalChangeSet.originalEntity;
|
|
713
716
|
if (current === meta) {
|
|
714
|
-
cs.originalEntity = originalChangeSet.originalEntity;
|
|
715
717
|
leafCs = cs;
|
|
716
718
|
}
|
|
717
719
|
else {
|
|
@@ -1208,7 +1210,8 @@ export class UnitOfWork {
|
|
|
1208
1210
|
const addToGroup = (cs) => {
|
|
1209
1211
|
// Skip stub TPT changesets with empty payload (e.g. leaf with no own-property changes on UPDATE)
|
|
1210
1212
|
if ((cs.type === ChangeSetType.UPDATE || cs.type === ChangeSetType.UPDATE_EARLY) &&
|
|
1211
|
-
!Utils.hasObjectKeys(cs.payload)
|
|
1213
|
+
!Utils.hasObjectKeys(cs.payload) &&
|
|
1214
|
+
!cs.meta.hasOptimisticLock()) {
|
|
1212
1215
|
return;
|
|
1213
1216
|
}
|
|
1214
1217
|
const group = groups[cs.type];
|
|
@@ -80,6 +80,8 @@ export declare class EntityComparator {
|
|
|
80
80
|
private getGenericComparator;
|
|
81
81
|
private getPropertyComparator;
|
|
82
82
|
private wrap;
|
|
83
|
+
/** Renders a key as a single-quoted JS string literal, safe to embed in generated code. */
|
|
84
|
+
private quote;
|
|
83
85
|
private safeKey;
|
|
84
86
|
/**
|
|
85
87
|
* Sets the toArray helper in the context if not already set.
|
|
@@ -758,10 +758,14 @@ export class EntityComparator {
|
|
|
758
758
|
return this.getGenericComparator(this.wrap(prop.name), `!equals(last${this.wrap(prop.name)}, current${this.wrap(prop.name)})`);
|
|
759
759
|
}
|
|
760
760
|
wrap(key) {
|
|
761
|
-
if (/^\[
|
|
761
|
+
if (/^\[idx_\d+]$/.exec(key)) {
|
|
762
762
|
return key;
|
|
763
763
|
}
|
|
764
|
-
return /^\w+$/.exec(key) ? `.${key}` : `[
|
|
764
|
+
return /^\w+$/.exec(key) ? `.${key}` : `[${this.quote(key)}]`;
|
|
765
|
+
}
|
|
766
|
+
/** Renders a key as a single-quoted JS string literal, safe to embed in generated code. */
|
|
767
|
+
quote(key) {
|
|
768
|
+
return `'${key.replace(/\\/g, '\\\\').replace(/'/g, "\\'")}'`;
|
|
765
769
|
}
|
|
766
770
|
safeKey(key) {
|
|
767
771
|
return key.replace(/\W/g, '_');
|
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.1.
|
|
156
|
+
static #ORM_VERSION = '7.1.16-dev.0';
|
|
157
157
|
/**
|
|
158
158
|
* Checks if the argument is instance of `Object`. Returns false for arrays.
|
|
159
159
|
*/
|
package/utils/upsert-utils.js
CHANGED
|
@@ -138,7 +138,14 @@ function getPropertyValue(obj, key) {
|
|
|
138
138
|
}
|
|
139
139
|
/** @internal */
|
|
140
140
|
export function getWhereCondition(meta, onConflictFields, data, where) {
|
|
141
|
-
|
|
141
|
+
// TPT children do not inherit the unique flags and indexes of their parent tables
|
|
142
|
+
const uniqueProps = new Set();
|
|
143
|
+
const uniques = [];
|
|
144
|
+
for (let current = meta; current; current = current.tptParent) {
|
|
145
|
+
current.props.filter(p => p.unique).forEach(p => uniqueProps.add(p.name));
|
|
146
|
+
uniques.push(...current.uniques);
|
|
147
|
+
}
|
|
148
|
+
const unique = onConflictFields ?? [...uniqueProps];
|
|
142
149
|
const propIndex = !isRaw(unique) &&
|
|
143
150
|
unique.findIndex(p => data[p] ?? data[p.substring(0, p.indexOf('.'))] != null);
|
|
144
151
|
if (onConflictFields || where == null) {
|
|
@@ -152,8 +159,8 @@ export function getWhereCondition(meta, onConflictFields, data, where) {
|
|
|
152
159
|
}
|
|
153
160
|
where = { [key]: getPropertyValue(data, unique[propIndex]) };
|
|
154
161
|
}
|
|
155
|
-
else
|
|
156
|
-
for (const u of
|
|
162
|
+
else {
|
|
163
|
+
for (const u of uniques) {
|
|
157
164
|
if (Utils.asArray(u.properties).every(p => data[p] != null)) {
|
|
158
165
|
where = Utils.asArray(u.properties).reduce((o, key) => {
|
|
159
166
|
o[key] = data[key];
|