@mikro-orm/core 7.1.15-dev.13 → 7.1.15-dev.15

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mikro-orm/core",
3
- "version": "7.1.15-dev.13",
3
+ "version": "7.1.15-dev.15",
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
- for (const key of meta.concurrencyCheckKeys) {
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 && meta.concurrencyCheckKeys.size > 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.concurrencyCheckKeys.size === 0 &&
307
- (!meta.versionProperty || changeSet.entity[meta.versionProperty] == null)) {
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.versionProperty) {
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
- if (meta.concurrencyCheckKeys.size === 0 &&
318
- (!meta.versionProperty || changeSets.every(cs => cs.entity[meta.versionProperty] == null))) {
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, meta.primaryKeys.concat(...meta.concurrencyCheckKeys));
325
- if (meta.versionProperty) {
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
- const res = await this.#driver.find(meta.root.class, { $or }, options);
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.versionProperty || meta.concurrencyCheckKeys.size > 0) && res && !res.affectedRows) {
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.versionProperty && !this.#usesReturningStatement ? [meta.properties[meta.versionProperty]] : [];
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
- if (!isCreate && Object.keys(payload).length === 0) {
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];
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.15-dev.13';
156
+ static #ORM_VERSION = '7.1.15-dev.15';
157
157
  /**
158
158
  * Checks if the argument is instance of `Object`. Returns false for arrays.
159
159
  */