@mikro-orm/core 7.1.13-dev.0 → 7.1.13-dev.10

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 CHANGED
@@ -1,4 +1,4 @@
1
- import { getOnConflictReturningFields, getWhereCondition, resetUntouchedCollections } from './utils/upsert-utils.js';
1
+ import { getOnConflictReturningFields, getOnCreateGeneratedFields, getWhereCondition, resetUntouchedCollections, } from './utils/upsert-utils.js';
2
2
  import { Utils } from './utils/Utils.js';
3
3
  import { Cursor } from './utils/Cursor.js';
4
4
  import { QueryHelper } from './utils/QueryHelper.js';
@@ -793,6 +793,7 @@ export class EntityManager {
793
793
  }
794
794
  const meta = this.metadata.get(entityName);
795
795
  const convertCustomTypes = !Utils.isEntity(data);
796
+ let generatedFields = [];
796
797
  if (Utils.isEntity(data)) {
797
798
  entity = data;
798
799
  if (helper(entity).__managed && helper(entity).__em === em && !this.config.get('upsertManaged')) {
@@ -800,6 +801,7 @@ export class EntityManager {
800
801
  return entity;
801
802
  }
802
803
  where = helper(entity).getPrimaryKey();
804
+ generatedFields = getOnCreateGeneratedFields(meta, entity);
803
805
  em.#entityFactory.assignDefaultValues(entity, meta);
804
806
  data = em.#comparator.prepareEntity(entity);
805
807
  }
@@ -812,6 +814,7 @@ export class EntityManager {
812
814
  return em.assign(exists, data);
813
815
  }
814
816
  }
817
+ generatedFields = getOnCreateGeneratedFields(meta, data);
815
818
  em.#entityFactory.assignDefaultValues(data, meta, true);
816
819
  for (const key of Object.keys(data)) {
817
820
  const prop = meta.properties[key];
@@ -820,6 +823,10 @@ export class EntityManager {
820
823
  }
821
824
  }
822
825
  }
826
+ // `onCreate` generated values are for the insert clause only, they must not overwrite an existing row
827
+ if (generatedFields.length > 0 && !options.onConflictMergeFields) {
828
+ options.onConflictExcludeFields = [...(options.onConflictExcludeFields ?? []), ...generatedFields];
829
+ }
823
830
  where = getWhereCondition(meta, options.onConflictFields, data, where).where;
824
831
  data = QueryHelper.processObjectParams(data);
825
832
  validateParams(data, 'insert data');
@@ -939,6 +946,7 @@ export class EntityManager {
939
946
  }
940
947
  const meta = this.metadata.get(entityName);
941
948
  const convertCustomTypes = !Utils.isEntity(data[0]);
949
+ const generatedFields = new Set();
942
950
  const allData = [];
943
951
  const allWhere = [];
944
952
  const entities = new Map();
@@ -956,6 +964,7 @@ export class EntityManager {
956
964
  continue;
957
965
  }
958
966
  where = helper(entity).getPrimaryKey();
967
+ getOnCreateGeneratedFields(meta, entity).forEach(field => generatedFields.add(field));
959
968
  em.#entityFactory.assignDefaultValues(entity, meta);
960
969
  entitiesByAllDataIdx.set(allData.length, entity);
961
970
  row = em.#comparator.prepareEntity(entity);
@@ -972,6 +981,7 @@ export class EntityManager {
972
981
  continue;
973
982
  }
974
983
  }
984
+ getOnCreateGeneratedFields(meta, row).forEach(field => generatedFields.add(field));
975
985
  em.#entityFactory.assignDefaultValues(row, meta, true);
976
986
  for (const key of Object.keys(row)) {
977
987
  const prop = meta.properties[key];
@@ -1000,6 +1010,10 @@ export class EntityManager {
1000
1010
  if (entities.size === data.length) {
1001
1011
  return [...entities.keys()];
1002
1012
  }
1013
+ // `onCreate` generated values are for the insert clause only, they must not overwrite existing rows
1014
+ if (generatedFields.size > 0 && !options.onConflictMergeFields) {
1015
+ options.onConflictExcludeFields = [...(options.onConflictExcludeFields ?? []), ...generatedFields];
1016
+ }
1003
1017
  if (em.eventManager.hasListeners(EventType.beforeUpsert, meta)) {
1004
1018
  for (const dto of data) {
1005
1019
  const entity = entitiesByData.get(dto) ?? dto;
@@ -445,7 +445,10 @@ export class EntityLoader {
445
445
  }
446
446
  }
447
447
  }
448
- if ([ReferenceKind.ONE_TO_ONE, ReferenceKind.MANY_TO_ONE].includes(prop.kind) && items.length !== children.length) {
448
+ // a missing target row means an orphaned reference, unless the query was narrowed by a populate condition
449
+ if ([ReferenceKind.ONE_TO_ONE, ReferenceKind.MANY_TO_ONE].includes(prop.kind) &&
450
+ items.length !== children.length &&
451
+ Utils.isEmpty(options.where)) {
449
452
  const nullVal = this.#em.config.get('forceUndefined') ? undefined : null;
450
453
  const itemsMap = new Set();
451
454
  const childrenMap = new Set();
@@ -667,7 +670,8 @@ export class EntityLoader {
667
670
  }
668
671
  return cond;
669
672
  });
670
- if (child.length > 0) {
673
+ // partial extraction from `$or` is unsound — the parent may have matched via a dropped branch
674
+ if (child.length > 0 && (op === '$and' || child.length === where[op].length)) {
671
675
  subCond[op] = child;
672
676
  }
673
677
  }
@@ -451,10 +451,13 @@ export class MetadataDiscovery {
451
451
  initManyToOneFieldName(prop, name) {
452
452
  const meta2 = prop.targetMeta;
453
453
  const ret = [];
454
- for (const primaryKey of meta2.primaryKeys) {
455
- this.initFieldName(meta2.properties[primaryKey]);
456
- for (const fieldName of meta2.properties[primaryKey].fieldNames) {
457
- ret.push(this.#namingStrategy.joinKeyColumnName(name, fieldName, meta2.compositePK));
454
+ // with `targetKey` on a composite PK target, derive the FK field name from that property
455
+ // instead of the PKs (simple PK targets keep the PK based naming for backwards compatibility)
456
+ const referencedKeys = prop.targetKey && meta2.compositePK ? [prop.targetKey] : meta2.primaryKeys;
457
+ for (const referencedKey of referencedKeys) {
458
+ this.initFieldName(meta2.properties[referencedKey]);
459
+ for (const fieldName of meta2.properties[referencedKey].fieldNames) {
460
+ ret.push(this.#namingStrategy.joinKeyColumnName(name, fieldName, !prop.targetKey && meta2.compositePK));
458
461
  }
459
462
  }
460
463
  return ret;
@@ -1050,11 +1053,12 @@ export class MetadataDiscovery {
1050
1053
  }
1051
1054
  // TPT children have their own tables that don't contain the parent's columns,
1052
1055
  // so propagating parent indexes/uniques/checks/triggers would target missing columns.
1056
+ // deep equality, as the subclass items might be copies of the base class ones (e.g. with TC39 decorators)
1053
1057
  if (meta.inheritanceType !== 'tpt' || !meta.tptParent) {
1054
- meta.indexes = Utils.unique([...base.indexes, ...meta.indexes]);
1055
- meta.uniques = Utils.unique([...base.uniques, ...meta.uniques]);
1056
- meta.checks = Utils.unique([...base.checks, ...meta.checks]);
1057
- meta.triggers = Utils.unique([...base.triggers, ...meta.triggers]);
1058
+ meta.indexes = Utils.unique([...base.indexes, ...meta.indexes], Utils.equals);
1059
+ meta.uniques = Utils.unique([...base.uniques, ...meta.uniques], Utils.equals);
1060
+ meta.checks = Utils.unique([...base.checks, ...meta.checks], Utils.equals);
1061
+ meta.triggers = Utils.unique([...base.triggers, ...meta.triggers], Utils.equals);
1058
1062
  }
1059
1063
  const pks = Object.values(meta.properties)
1060
1064
  .filter(p => p.primary)
@@ -2075,6 +2079,7 @@ export class MetadataDiscovery {
2075
2079
  prop.columnTypes.push(...columnTypes);
2076
2080
  if (!targetMeta.compositePK || prop.targetKey) {
2077
2081
  prop.customType = referencedProp.customType;
2082
+ prop.collation ??= referencedProp.collation;
2078
2083
  }
2079
2084
  }
2080
2085
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mikro-orm/core",
3
- "version": "7.1.13-dev.0",
3
+ "version": "7.1.13-dev.10",
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.d.ts CHANGED
@@ -58,7 +58,7 @@ export declare class Utils {
58
58
  /**
59
59
  * Gets array without duplicates.
60
60
  */
61
- static unique<T = string>(items: T[]): T[];
61
+ static unique<T = string>(items: T[], equals?: (a: T, b: T) => boolean): T[];
62
62
  /**
63
63
  * Merges all sources into the target recursively.
64
64
  */
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.13-dev.0';
156
+ static #ORM_VERSION = '7.1.13-dev.10';
157
157
  /**
158
158
  * Checks if the argument is instance of `Object`. Returns false for arrays.
159
159
  */
@@ -216,10 +216,13 @@ export class Utils {
216
216
  /**
217
217
  * Gets array without duplicates.
218
218
  */
219
- static unique(items) {
219
+ static unique(items, equals) {
220
220
  if (items.length < 2) {
221
221
  return items;
222
222
  }
223
+ if (equals) {
224
+ return items.filter((a, idx) => items.findIndex(b => equals(a, b)) === idx);
225
+ }
223
226
  return [...new Set(items)];
224
227
  }
225
228
  /**
@@ -1,8 +1,16 @@
1
- import type { EntityData, EntityMetadata, FilterQuery } from '../typings.js';
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
4
  /** @internal */
5
5
  export declare function getOnConflictFields<T>(meta: EntityMetadata<T> | undefined, data: EntityData<T>, uniqueFields: (keyof T)[] | Raw, options: UpsertOptions<T>): (keyof T)[];
6
+ /**
7
+ * Detects properties that will get their value generated by an `onCreate` hook during the upsert,
8
+ * i.e. those with an `onCreate` hook and no value provided. Such values are meant for the insert
9
+ * clause only and must not overwrite an existing row via the `on conflict do update set` clause.
10
+ * The property filter mirrors `EntityFactory.assignDefaultValues`.
11
+ * @internal
12
+ */
13
+ export declare function getOnCreateGeneratedFields<T extends object>(meta: EntityMetadata<T>, data: T | EntityData<T>): EntityKey<T>[];
6
14
  /** @internal */
7
15
  export declare function getOnConflictReturningFields<T, P extends string>(meta: EntityMetadata<T> | undefined, data: EntityData<T>, uniqueFields: (keyof T)[] | Raw, options: UpsertOptions<T, P>): (keyof T)[] | '*';
8
16
  /** @internal */
@@ -72,6 +72,22 @@ export function getOnConflictFields(meta, data, uniqueFields, options) {
72
72
  }
73
73
  return keys;
74
74
  }
75
+ /**
76
+ * Detects properties that will get their value generated by an `onCreate` hook during the upsert,
77
+ * i.e. those with an `onCreate` hook and no value provided. Such values are meant for the insert
78
+ * clause only and must not overwrite an existing row via the `on conflict do update set` clause.
79
+ * The property filter mirrors `EntityFactory.assignDefaultValues`.
80
+ * @internal
81
+ */
82
+ export function getOnCreateGeneratedFields(meta, data) {
83
+ return meta.props
84
+ .filter(prop => prop.onCreate &&
85
+ !prop.embedded &&
86
+ ![ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind) &&
87
+ !(prop.getter && !prop.setter) &&
88
+ data[prop.name] == null)
89
+ .map(prop => prop.name);
90
+ }
75
91
  /** @internal */
76
92
  export function getOnConflictReturningFields(meta, data, uniqueFields, options) {
77
93
  /* v8 ignore next */