@mikro-orm/sql 7.1.9-dev.22 → 7.1.9-dev.24

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.
@@ -1316,7 +1316,7 @@ export class AbstractSqlDriver extends DatabaseDriver {
1316
1316
  }
1317
1317
  }
1318
1318
  }
1319
- return this.buildPivotResultMap(owners, res, pivotProp2.name, pivotProp1.name);
1319
+ return this.buildPivotResultMap(owners, res, pivotProp2.name, pivotProp1.name, ownerMeta);
1320
1320
  }
1321
1321
  /**
1322
1322
  * Load from a polymorphic M:N pivot table.
@@ -1337,10 +1337,15 @@ export class AbstractSqlDriver extends DatabaseDriver {
1337
1337
  async loadPolymorphicPivotOwnerSide(prop, owners, where, orderBy, ctx, options, pivotJoin, inverseProp) {
1338
1338
  const pivotMeta = this.metadata.get(prop.pivotEntity);
1339
1339
  const targetMeta = prop.targetMeta;
1340
- // Build condition: discriminator = 'post' AND {discriminator} IN (...)
1340
+ // `prop.discriminator` spans all owner FK columns but carries no target metadata, so a composite
1341
+ // owner PK neither expands to a tuple condition nor gets mapped back; the virtual M:1 relation
1342
+ // to this discriminator's owner describes the same columns as an actual relation
1343
+ const ownerMeta = this.metadata.get(pivotMeta.polymorphicDiscriminatorMap[prop.discriminatorValue]);
1344
+ const ownerProp = pivotMeta.properties[`${prop.discriminator}_${ownerMeta.tableName}`];
1345
+ // Build condition: discriminator = 'post' AND {owner} IN (...)
1341
1346
  const cond = {
1342
1347
  [prop.discriminatorColumn]: prop.discriminatorValue,
1343
- [prop.discriminator]: { $in: owners.length === 1 && owners[0].length === 1 ? owners.map(o => o[0]) : owners },
1348
+ [ownerProp.name]: { $in: owners.length === 1 && owners[0].length === 1 ? owners.map(o => o[0]) : owners },
1344
1349
  };
1345
1350
  if (!Utils.isEmpty(where)) {
1346
1351
  cond[inverseProp.name] = { ...where };
@@ -1351,9 +1356,12 @@ export class AbstractSqlDriver extends DatabaseDriver {
1351
1356
  const childExclude = !Utils.isEmpty(options?.exclude)
1352
1357
  ? options.exclude.map(f => `${inverseProp.name}.${f}`)
1353
1358
  : [];
1359
+ // the owner relation is virtual, so the FK columns have to be selected via the per-column pivot
1360
+ // props a composite owner PK gets; a single column owner PK is covered by the flat prop itself
1361
+ const ownerFields = ownerMeta.compositePK ? prop.joinColumns : [prop.discriminator];
1354
1362
  const fields = pivotJoin
1355
- ? [inverseProp.name, prop.discriminator, prop.discriminatorColumn]
1356
- : [inverseProp.name, prop.discriminator, prop.discriminatorColumn, ...childFields];
1363
+ ? [inverseProp.name, ...ownerFields, prop.discriminatorColumn]
1364
+ : [inverseProp.name, ...ownerFields, prop.discriminatorColumn, ...childFields];
1357
1365
  const res = await this.find(pivotMeta.class, cond, {
1358
1366
  ctx,
1359
1367
  ...options,
@@ -1374,7 +1382,7 @@ export class AbstractSqlDriver extends DatabaseDriver {
1374
1382
  _populateWhere: 'infer',
1375
1383
  populateFilter: this.wrapPopulateFilter(options, inverseProp.name),
1376
1384
  });
1377
- return this.buildPivotResultMap(owners, res, prop.discriminator, inverseProp.name);
1385
+ return this.buildPivotResultMap(owners, res, ownerProp.name, inverseProp.name, ownerMeta);
1378
1386
  }
1379
1387
  /**
1380
1388
  * Load from inverse side of polymorphic M:N (e.g., Tag -> Posts)
@@ -1423,7 +1431,7 @@ export class AbstractSqlDriver extends DatabaseDriver {
1423
1431
  _populateWhere: 'infer',
1424
1432
  populateFilter: this.wrapPopulateFilter(options, ownerRelationName),
1425
1433
  });
1426
- return this.buildPivotResultMap(owners, res, tagProp.name, ownerRelationName);
1434
+ return this.buildPivotResultMap(owners, res, tagProp.name, ownerRelationName, tagProp.targetMeta);
1427
1435
  }
1428
1436
  /**
1429
1437
  * Load a union-target polymorphic M:N pivot (e.g. Post.attachments -> Image | Video).
@@ -1519,19 +1527,23 @@ export class AbstractSqlDriver extends DatabaseDriver {
1519
1527
  }
1520
1528
  }
1521
1529
  const result = orphanedRows.size > 0 ? pivotRows.filter(r => !orphanedRows.has(r)) : pivotRows;
1522
- return this.buildPivotResultMap(owners, result, ownerProp.name, prop.discriminator);
1530
+ return this.buildPivotResultMap(owners, result, ownerProp.name, prop.discriminator, ownerMeta);
1523
1531
  }
1524
1532
  /**
1525
1533
  * Build a map from owner PKs to their related entities from pivot table results.
1526
1534
  */
1527
- buildPivotResultMap(owners, results, keyProp, valueProp) {
1535
+ buildPivotResultMap(owners, results, keyProp, valueProp, ownerMeta) {
1528
1536
  const map = {};
1529
1537
  for (const owner of owners) {
1530
1538
  const key = Utils.getPrimaryKeyHash(owner);
1531
1539
  map[key] = [];
1532
1540
  }
1533
1541
  for (const item of results) {
1534
- const key = Utils.getPrimaryKeyHash(Utils.asArray(item[keyProp]));
1542
+ const fk = item[keyProp];
1543
+ // the owner PKs are always flat, while the pivot FK follows the owner PK structure,
1544
+ // so a PK built from a relation to another composite PK entity needs flattening too
1545
+ const pks = ownerMeta && fk != null ? Utils.getOrderedPrimaryKeys(fk, ownerMeta) : Utils.asArray(fk);
1546
+ const key = Utils.getPrimaryKeyHash(pks);
1535
1547
  const entity = item[valueProp];
1536
1548
  if (map[key]) {
1537
1549
  map[key].push(entity);
@@ -1657,8 +1669,23 @@ export class AbstractSqlDriver extends DatabaseDriver {
1657
1669
  const [propName, ref] = hint.field.split(':', 2);
1658
1670
  return { propName, ref, children: hint.children };
1659
1671
  });
1672
+ // with `fixedOrder` the pivot PK is the order column, which is not guaranteed to be unique when the
1673
+ // pivot table is managed externally, so we disambiguate the rows by their FKs on top of the PK
1674
+ const pivotRelations = meta.pivotTable && !meta.compositePK
1675
+ ? meta.relations.filter(p => p.kind === ReferenceKind.MANY_TO_ONE && p.persist !== false)
1676
+ : [];
1660
1677
  for (const item of rawResults) {
1661
- const pk = Utils.getCompositeKeyHash(item, meta);
1678
+ let pk = Utils.getCompositeKeyHash(item, meta);
1679
+ if (pivotRelations.length > 0) {
1680
+ pk = Utils.getPrimaryKeyHash([
1681
+ pk,
1682
+ ...pivotRelations.flatMap(p => {
1683
+ const value = item[p.name];
1684
+ // composite FKs are mapped to an array of values, which `extractPK` does not accept
1685
+ return (Array.isArray(value) ? value : Utils.extractPK(value, p.targetMeta));
1686
+ }),
1687
+ ]);
1688
+ }
1662
1689
  if (map[pk]) {
1663
1690
  for (const { propName } of hints) {
1664
1691
  if (!item[propName]) {
@@ -2087,7 +2114,20 @@ export class AbstractSqlDriver extends DatabaseDriver {
2087
2114
  const ret = {};
2088
2115
  for (const prop of meta.relations) {
2089
2116
  if (prop.kind === ReferenceKind.MANY_TO_MANY && data[prop.name]) {
2090
- ret[prop.name] = data[prop.name].map((item) => Utils.asArray(item));
2117
+ // union targets are validated to have a single PK column, so a pivot row is always keyed
2118
+ // by exactly `[discriminator, pk]` - anything else cannot address a target table
2119
+ const discriminators = QueryHelper.isUnionTargetPolymorphic(prop)
2120
+ ? Object.keys(prop.discriminatorMap)
2121
+ : undefined;
2122
+ ret[prop.name] = data[prop.name].map((item) => {
2123
+ const values = Utils.asArray(item);
2124
+ if (discriminators && !(values.length === 2 && discriminators.includes('' + values[0]))) {
2125
+ throw new Error(`Cannot resolve the discriminator value of ${meta.className}.${prop.name} from '${values.join(', ')}', ` +
2126
+ `as the same primary key can exist in any of the target tables. ` +
2127
+ `Pass the target as a [discriminator, ...primaryKey] tuple, e.g. ${JSON.stringify([discriminators[0], ...values])}.`);
2128
+ }
2129
+ return values;
2130
+ });
2091
2131
  delete data[prop.name];
2092
2132
  }
2093
2133
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mikro-orm/sql",
3
- "version": "7.1.9-dev.22",
3
+ "version": "7.1.9-dev.24",
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",
@@ -53,7 +53,7 @@
53
53
  "@mikro-orm/core": "^7.1.8"
54
54
  },
55
55
  "peerDependencies": {
56
- "@mikro-orm/core": "7.1.9-dev.22"
56
+ "@mikro-orm/core": "7.1.9-dev.24"
57
57
  },
58
58
  "engines": {
59
59
  "node": ">= 22.17.0"