@mikro-orm/sql 7.1.15-dev.9 → 7.1.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.
@@ -118,13 +118,6 @@ export declare abstract class AbstractSqlDriver<Connection extends AbstractSqlCo
118
118
  mergeJoinedResult<T extends object>(rawResults: EntityData<T>[], meta: EntityMetadata<T>, joinedProps: PopulateOptions<T>[]): EntityData<T>[];
119
119
  protected shouldHaveColumn<T, U>(meta: EntityMetadata<T>, prop: EntityProperty<U>, populate: readonly PopulateOptions<U>[], fields?: readonly InternalField<U>[], exclude?: readonly InternalField<U>[]): boolean;
120
120
  protected getFieldsForJoinedLoad<T extends object>(qb: AnyQueryBuilder<T>, meta: EntityMetadata<T>, options: FieldsForJoinedLoadOptions<T>): InternalField<T>[];
121
- /**
122
- * Walks the TPT inheritance chain of `leafMeta` and INNER JOINs each parent table.
123
- * Registers the parent aliases in `qb.state.tptAlias` so column resolution finds them
124
- * when filter conditions reference parent-table columns.
125
- * @internal
126
- */
127
- protected addTPTParentJoinsForRelation<T extends object>(qb: AnyQueryBuilder<T>, leafMeta: EntityMetadata, leafAlias: string, basePath: string): void;
128
121
  /**
129
122
  * Adds LEFT JOINs and fields for TPT polymorphic loading when populating a relation to a TPT base class.
130
123
  * @internal
@@ -915,7 +915,11 @@ export class AbstractSqlDriver extends DatabaseDriver {
915
915
  if (!options.upsert && options.unionWhere?.length) {
916
916
  where = (await this.applyUnionWhere(meta, where, options, true));
917
917
  }
918
- if (Utils.hasObjectKeys(data)) {
918
+ if (options.upsert && meta.tptParent) {
919
+ res = await this.nativeUpdateMany(entityName, [where], [data], options);
920
+ }
921
+ else if (Utils.hasObjectKeys(data) || (meta.inheritanceType === 'tpt' && meta.ownsVersionProperty())) {
922
+ // a TPT table declaring the version property is bumped even when only other tables of the hierarchy changed
919
923
  const qb = this.createQueryBuilder(entityName, options.ctx, 'write', options.convertCustomTypes, options.loggerContext).withSchema(this.getSchemaName(meta, options));
920
924
  qb.setAbortOptions(pickAbortOptions(options));
921
925
  if (options.upsert) {
@@ -941,7 +945,7 @@ export class AbstractSqlDriver extends DatabaseDriver {
941
945
  qb.update(data).where(where);
942
946
  // reload generated columns and version fields
943
947
  const returning = [];
944
- meta.props
948
+ this.getTableProps(meta)
945
949
  .filter(prop => (prop.generated && !prop.primary) || prop.version)
946
950
  .forEach(prop => returning.push(prop.name));
947
951
  qb.returning(returning);
@@ -957,13 +961,38 @@ export class AbstractSqlDriver extends DatabaseDriver {
957
961
  options.convertCustomTypes ??= true;
958
962
  const meta = this.metadata.get(entityName);
959
963
  if (options.upsert) {
964
+ if (meta.tptParent) {
965
+ // TPT parent tables go first, the PK they provide is the conflict target of this table
966
+ await this.nativeUpdateMany(meta.tptParent.class, where, data, options);
967
+ for (const [i, row] of data.entries()) {
968
+ if (meta.primaryKeys.some(pk => row[pk] == null)) {
969
+ const found = await this.findOne(meta.tptParent.class, where[i], {
970
+ fields: meta.primaryKeys,
971
+ ctx: options.ctx,
972
+ connectionType: 'write',
973
+ schema: options.schema,
974
+ });
975
+ meta.primaryKeys.forEach(pk => (row[pk] = found?.[pk]));
976
+ }
977
+ }
978
+ options = { ...options, onConflictFields: meta.primaryKeys, onConflictWhere: undefined };
979
+ }
960
980
  const uniqueFields = options.onConflictFields ??
961
981
  (Utils.isPlainObject(where[0])
962
982
  ? Object.keys(where[0]).flatMap(key => Utils.splitPrimaryKeys(key))
963
983
  : meta.primaryKeys);
964
984
  const qb = this.createQueryBuilder(entityName, options.ctx, 'write', options.convertCustomTypes, options.loggerContext).withSchema(this.getSchemaName(meta, options));
965
985
  qb.setAbortOptions(pickAbortOptions(options));
966
- const returning = getOnConflictReturningFields(meta, data[0], uniqueFields, options);
986
+ let returning = getOnConflictReturningFields(meta, data[0], uniqueFields, options);
987
+ if (meta.inheritanceType === 'tpt') {
988
+ // each TPT table only carries its own columns, the entity is reloaded instead of mapping the returned rows
989
+ const own = (key) => meta.primaryKeys.includes(key) ||
990
+ this.getTableProps(meta).some(prop => prop.name === key.split('.')[0]);
991
+ data = data.map(row => Object.fromEntries(Object.entries(row).filter(([key]) => own(key))));
992
+ options.onConflictMergeFields = options.onConflictMergeFields?.filter(f => own(f));
993
+ options.onConflictExcludeFields = options.onConflictExcludeFields?.filter(f => own(f));
994
+ returning = [];
995
+ }
967
996
  qb.insert(data)
968
997
  .onConflict(uniqueFields)
969
998
  .returning(returning);
@@ -977,7 +1006,8 @@ export class AbstractSqlDriver extends DatabaseDriver {
977
1006
  if (options.onConflictWhere) {
978
1007
  qb.where(options.onConflictWhere);
979
1008
  }
980
- return this.rethrow(qb.execute('run', false));
1009
+ const res = await this.rethrow(qb.execute('run', false));
1010
+ return meta.inheritanceType === 'tpt' ? { ...res, row: undefined, rows: [] } : res;
981
1011
  }
982
1012
  const collections = options.processCollections ? data.map(d => this.extractManyToMany(meta, d)) : [];
983
1013
  const keys = new Set();
@@ -992,7 +1022,10 @@ export class AbstractSqlDriver extends DatabaseDriver {
992
1022
  }
993
1023
  }
994
1024
  // reload generated columns and version fields
995
- meta.props.filter(prop => prop.generated || prop.version || prop.primary).forEach(prop => returning.add(prop.name));
1025
+ meta.getPrimaryProps().forEach(prop => returning.add(prop.name));
1026
+ this.getTableProps(meta)
1027
+ .filter(prop => prop.generated || prop.version)
1028
+ .forEach(prop => returning.add(prop.name));
996
1029
  const pkCond = Utils.flatten(meta.primaryKeys.map(pk => meta.properties[pk].fieldNames))
997
1030
  .map(pk => `${this.platform.quoteIdentifier(pk)} = ?`)
998
1031
  .join(' and ');
@@ -1050,7 +1083,7 @@ export class AbstractSqlDriver extends DatabaseDriver {
1050
1083
  return sql;
1051
1084
  });
1052
1085
  }
1053
- if (meta.versionProperty) {
1086
+ if (meta.ownsVersionProperty()) {
1054
1087
  const versionProperty = meta.properties[meta.versionProperty];
1055
1088
  const quotedFieldName = this.platform.quoteIdentifier(versionProperty.fieldNames[0]);
1056
1089
  sql += `${quotedFieldName} = `;
@@ -1063,7 +1096,7 @@ export class AbstractSqlDriver extends DatabaseDriver {
1063
1096
  sql += `, `;
1064
1097
  }
1065
1098
  sql = sql.substring(0, sql.length - 2) + ' where ';
1066
- const pkProps = meta.primaryKeys.concat(...meta.concurrencyCheckKeys);
1099
+ const pkProps = meta.primaryKeys.concat(...meta.getOwnConcurrencyCheckKeys());
1067
1100
  const pks = Utils.flatten(pkProps.map(pk => meta.properties[pk].fieldNames));
1068
1101
  const useTupleIn = pks.length <= 1 || this.platform.allowsComparingTuples();
1069
1102
  const condTemplate = useTupleIn
@@ -1784,7 +1817,7 @@ export class AbstractSqlDriver extends DatabaseDriver {
1784
1817
  // INNER JOINs get nested inside the polymorphic LEFT JOIN by processNestedJoins, which
1785
1818
  // keeps the resulting query valid for rows pointing to other polymorphic targets.
1786
1819
  if (targetMeta.inheritanceType === 'tpt' && targetMeta.tptParent) {
1787
- this.addTPTParentJoinsForRelation(qb, targetMeta, tableAlias, targetPath);
1820
+ qb.addTPTParentJoins(targetMeta, tableAlias, targetPath);
1788
1821
  }
1789
1822
  // For polymorphic targets that are TPT base classes, also LEFT JOIN
1790
1823
  // all descendant tables so child-specific fields can be selected.
@@ -1832,10 +1865,6 @@ export class AbstractSqlDriver extends DatabaseDriver {
1832
1865
  : JoinType.leftJoin;
1833
1866
  const schema = prop.targetMeta.schema === '*' ? (options?.schema ?? this.config.get('schema')) : prop.targetMeta.schema;
1834
1867
  qb.join(field, tableAlias, {}, joinType, path, schema);
1835
- // For relations to TPT child entities, INNER JOIN parent tables (GH #7469)
1836
- if (meta2.inheritanceType === 'tpt' && meta2.tptParent) {
1837
- this.addTPTParentJoinsForRelation(qb, meta2, tableAlias, path);
1838
- }
1839
1868
  // For relations to TPT base classes, add LEFT JOINs for all child tables (polymorphic loading)
1840
1869
  if (meta2.inheritanceType === 'tpt' && meta2.tptChildren?.length && !ref) {
1841
1870
  // Use the registry metadata to ensure allTPTDescendants is available
@@ -1882,25 +1911,6 @@ export class AbstractSqlDriver extends DatabaseDriver {
1882
1911
  }
1883
1912
  return fields;
1884
1913
  }
1885
- /**
1886
- * Walks the TPT inheritance chain of `leafMeta` and INNER JOINs each parent table.
1887
- * Registers the parent aliases in `qb.state.tptAlias` so column resolution finds them
1888
- * when filter conditions reference parent-table columns.
1889
- * @internal
1890
- */
1891
- addTPTParentJoinsForRelation(qb, leafMeta, leafAlias, basePath) {
1892
- let childAlias = leafAlias;
1893
- let childMeta = leafMeta;
1894
- while (childMeta.tptParent) {
1895
- const parentMeta = childMeta.tptParent;
1896
- const parentAlias = qb.getNextAlias(parentMeta.className);
1897
- qb.createAlias(parentMeta.class, parentAlias);
1898
- qb.state.tptAlias[`${leafAlias}:${parentMeta.className}`] = parentAlias;
1899
- qb.addPropertyJoin(childMeta.tptParentProp, childAlias, parentAlias, JoinType.innerJoin, `${basePath}.[tpt]${childMeta.className}`);
1900
- childAlias = parentAlias;
1901
- childMeta = parentMeta;
1902
- }
1903
- }
1904
1914
  /**
1905
1915
  * Adds LEFT JOINs and fields for TPT polymorphic loading when populating a relation to a TPT base class.
1906
1916
  * @internal
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mikro-orm/sql",
3
- "version": "7.1.15-dev.9",
3
+ "version": "7.1.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",
@@ -50,10 +50,10 @@
50
50
  "kysely": "0.29.5"
51
51
  },
52
52
  "devDependencies": {
53
- "@mikro-orm/core": "^7.1.14"
53
+ "@mikro-orm/core": "^7.1.15"
54
54
  },
55
55
  "peerDependencies": {
56
- "@mikro-orm/core": "7.1.15-dev.9"
56
+ "@mikro-orm/core": "7.1.15"
57
57
  },
58
58
  "engines": {
59
59
  "node": ">= 22.17.0"
@@ -950,6 +950,13 @@ export declare class QueryBuilder<Entity extends object = AnyEntity, RootAlias e
950
950
  */
951
951
  addPropertyJoin(prop: EntityProperty, ownerAlias: string, alias: string, type: JoinType, path: string, schema?: string): string;
952
952
  private joinReference;
953
+ /**
954
+ * Walks the TPT inheritance chain of `leafMeta` and INNER JOINs each parent table.
955
+ * Registers the parent aliases in `state.tptAlias` so column resolution finds them
956
+ * when conditions reference parent-table columns.
957
+ * @internal
958
+ */
959
+ addTPTParentJoins(leafMeta: EntityMetadata, leafAlias: string, basePath: string): void;
953
960
  protected prepareFields<T>(fields: InternalField<T>[], type?: 'where' | 'groupBy' | 'sub-query', schema?: string): (string | RawQueryFragment)[];
954
961
  /**
955
962
  * Resolves nested paths like `a.books.title` to their actual field references.
@@ -495,7 +495,10 @@ export class QueryBuilder {
495
495
  return cond.some(c => this.condReferencesAlias(c, alias));
496
496
  }
497
497
  if (Utils.isPlainObject(cond)) {
498
- return Object.entries(cond).some(([key, value]) => key.startsWith(`${alias}.`) || this.condReferencesAlias(value, alias));
498
+ return Object.entries(cond).some(([key, value]) => {
499
+ const [keyAlias, field] = this.helper.splitField(key);
500
+ return this.helper.getTPTAliasForProperty(field, keyAlias) === alias || this.condReferencesAlias(value, alias);
501
+ });
499
502
  }
500
503
  return false;
501
504
  }
@@ -904,6 +907,8 @@ export class QueryBuilder {
904
907
  if (this.type === QueryType.INSERT) {
905
908
  const returningProps = meta.hydrateProps
906
909
  .filter(prop => prop.returning || (prop.persist !== false && ((prop.primary && prop.autoincrement) || prop.defaultRaw)))
910
+ // a TPT table can only return its own columns
911
+ .filter(prop => meta.inheritanceType !== 'tpt' || prop.primary || meta.ownProps.some(p => p.name === prop.name))
907
912
  .filter(prop => !data || !(prop.name in data));
908
913
  if (returningProps.length > 0) {
909
914
  qb.returning(Utils.flatten(returningProps.map(prop => prop.fieldNames)));
@@ -1493,9 +1498,31 @@ export class QueryBuilder {
1493
1498
  this.#state.joins[aliasedName] = this.helper.joinManyToOneReference(prop, ownerAlias, alias, type, cond, schema);
1494
1499
  this.#state.joins[aliasedName].path ??= path;
1495
1500
  }
1501
+ if (prop.targetMeta.inheritanceType === 'tpt' && prop.targetMeta.tptParent) {
1502
+ this.addTPTParentJoins(prop.targetMeta, alias, path);
1503
+ }
1496
1504
  this.nestReferencedJoins(this.#state.joins[aliasedName]);
1497
1505
  return { prop, key: aliasedName };
1498
1506
  }
1507
+ /**
1508
+ * Walks the TPT inheritance chain of `leafMeta` and INNER JOINs each parent table.
1509
+ * Registers the parent aliases in `state.tptAlias` so column resolution finds them
1510
+ * when conditions reference parent-table columns.
1511
+ * @internal
1512
+ */
1513
+ addTPTParentJoins(leafMeta, leafAlias, basePath) {
1514
+ let childAlias = leafAlias;
1515
+ let childMeta = leafMeta;
1516
+ while (childMeta.tptParent) {
1517
+ const parentMeta = childMeta.tptParent;
1518
+ const parentAlias = this.getNextAlias(parentMeta.className);
1519
+ this.createAlias(parentMeta.class, parentAlias);
1520
+ this.#state.tptAlias[`${leafAlias}:${parentMeta.className}`] = parentAlias;
1521
+ this.addPropertyJoin(childMeta.tptParentProp, childAlias, parentAlias, JoinType.innerJoin, `${basePath}.[tpt]${childMeta.className}`);
1522
+ childAlias = parentAlias;
1523
+ childMeta = parentMeta;
1524
+ }
1525
+ }
1499
1526
  prepareFields(fields, type = 'where', schema) {
1500
1527
  const ret = [];
1501
1528
  const getFieldName = (name, customAlias) => {
@@ -2085,6 +2112,7 @@ export class QueryBuilder {
2085
2112
  else {
2086
2113
  join.cond = { ...join.cond, [k]: cond[k] };
2087
2114
  }
2115
+ this.nestReferencedJoins(join);
2088
2116
  }
2089
2117
  }
2090
2118
  }
@@ -763,7 +763,7 @@ export class QueryBuilderHelper {
763
763
  }
764
764
  updateVersionProperty(qb, data) {
765
765
  const meta = this.#metadata.find(this.#entityName);
766
- if (!meta?.versionProperty || meta.versionProperty in data) {
766
+ if (!meta?.ownsVersionProperty() || meta.versionProperty in data) {
767
767
  return;
768
768
  }
769
769
  const versionProperty = meta.properties[meta.versionProperty];