@mikro-orm/sql 7.1.10-dev.1 → 7.1.10-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/AbstractSqlDriver.js +3 -2
- package/package.json +2 -2
- package/query/QueryBuilder.d.ts +11 -0
- package/query/QueryBuilder.js +44 -16
- package/schema/SchemaHelper.d.ts +2 -0
- package/schema/SchemaHelper.js +6 -2
package/AbstractSqlDriver.js
CHANGED
|
@@ -1675,14 +1675,15 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
1675
1675
|
// (including virtual ones, as the owner FK of a polymorphic pivot is only mapped via non-persisted relations)
|
|
1676
1676
|
const pivotRelations = meta.pivotTable && !meta.compositePK ? meta.relations.filter(p => p.kind === ReferenceKind.MANY_TO_ONE) : [];
|
|
1677
1677
|
for (const item of rawResults) {
|
|
1678
|
-
|
|
1678
|
+
// flat hash, so nested composite PK values keep their own separators and cannot collide
|
|
1679
|
+
let pk = Utils.getCompositeKeyHash(item, meta, false, undefined, true);
|
|
1679
1680
|
if (pivotRelations.length > 0) {
|
|
1680
1681
|
pk = Utils.getPrimaryKeyHash([
|
|
1681
1682
|
pk,
|
|
1682
1683
|
...pivotRelations.flatMap(p => {
|
|
1683
1684
|
const value = item[p.name];
|
|
1684
1685
|
// 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
|
+
return (Array.isArray(value) ? Utils.flatten(value, true) : Utils.extractPK(value, p.targetMeta));
|
|
1686
1687
|
}),
|
|
1687
1688
|
]);
|
|
1688
1689
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mikro-orm/sql",
|
|
3
|
-
"version": "7.1.10-dev.
|
|
3
|
+
"version": "7.1.10-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",
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
"@mikro-orm/core": "^7.1.9"
|
|
54
54
|
},
|
|
55
55
|
"peerDependencies": {
|
|
56
|
-
"@mikro-orm/core": "7.1.10-dev.
|
|
56
|
+
"@mikro-orm/core": "7.1.10-dev.10"
|
|
57
57
|
},
|
|
58
58
|
"engines": {
|
|
59
59
|
"node": ">= 22.17.0"
|
package/query/QueryBuilder.d.ts
CHANGED
|
@@ -506,6 +506,17 @@ export declare class QueryBuilder<Entity extends object = AnyEntity, RootAlias e
|
|
|
506
506
|
* @internal
|
|
507
507
|
*/
|
|
508
508
|
applyJoinedFilters(em: EntityManager, filterOptions: FilterOptions | undefined): Promise<void>;
|
|
509
|
+
/**
|
|
510
|
+
* The `on` clause of `condJoin` — its explicit join condition or a filter condition merged into
|
|
511
|
+
* it — can reference the alias of any join in its subtree, both auto-joins created while
|
|
512
|
+
* processing the condition and pre-existing joined paths, all of which render after `condJoin`
|
|
513
|
+
* and would be forward alias references (issues #7681, #8090, #8099). When that happens, fold the
|
|
514
|
+
* subtree into `condJoin`, so it renders as a single parenthesized join group and every alias
|
|
515
|
+
* shares the scope of the outer `on` clause.
|
|
516
|
+
*/
|
|
517
|
+
private nestReferencedJoins;
|
|
518
|
+
private getJoinSubtree;
|
|
519
|
+
private condReferencesAlias;
|
|
509
520
|
withSubQuery(subQuery: RawQueryFragment | NativeQueryBuilder, alias: string): this;
|
|
510
521
|
/**
|
|
511
522
|
* Adds a WHERE clause to the query using an object condition.
|
package/query/QueryBuilder.js
CHANGED
|
@@ -433,6 +433,7 @@ export class QueryBuilder {
|
|
|
433
433
|
else {
|
|
434
434
|
join.cond = { ...cond };
|
|
435
435
|
}
|
|
436
|
+
this.nestReferencedJoins(join);
|
|
436
437
|
// For polymorphic LEFT JOIN filters, add a WHERE condition to enforce the filter
|
|
437
438
|
// only for rows matching this target's discriminator value. This ensures rows pointing
|
|
438
439
|
// to other polymorphic targets are not excluded.
|
|
@@ -456,6 +457,45 @@ export class QueryBuilder {
|
|
|
456
457
|
}
|
|
457
458
|
}
|
|
458
459
|
}
|
|
460
|
+
/**
|
|
461
|
+
* The `on` clause of `condJoin` — its explicit join condition or a filter condition merged into
|
|
462
|
+
* it — can reference the alias of any join in its subtree, both auto-joins created while
|
|
463
|
+
* processing the condition and pre-existing joined paths, all of which render after `condJoin`
|
|
464
|
+
* and would be forward alias references (issues #7681, #8090, #8099). When that happens, fold the
|
|
465
|
+
* subtree into `condJoin`, so it renders as a single parenthesized join group and every alias
|
|
466
|
+
* shares the scope of the outer `on` clause.
|
|
467
|
+
*/
|
|
468
|
+
nestReferencedJoins(condJoin) {
|
|
469
|
+
// m:n pivot joins might not have the target join entry created
|
|
470
|
+
if (!condJoin) {
|
|
471
|
+
return;
|
|
472
|
+
}
|
|
473
|
+
const subtree = this.getJoinSubtree(condJoin);
|
|
474
|
+
if (!subtree.some(j => this.condReferencesAlias(condJoin.cond, j.alias))) {
|
|
475
|
+
return;
|
|
476
|
+
}
|
|
477
|
+
for (const j of subtree) {
|
|
478
|
+
const parent = j.ownerAlias === condJoin.alias ? condJoin : subtree.find(p => p.alias === j.ownerAlias);
|
|
479
|
+
if (!parent.nested?.has(j)) {
|
|
480
|
+
const nested = (parent.nested ??= new Set());
|
|
481
|
+
j.type = j.type === JoinType.innerJoin ? JoinType.nestedInnerJoin : JoinType.nestedLeftJoin;
|
|
482
|
+
nested.add(j);
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
getJoinSubtree(join) {
|
|
487
|
+
const children = Object.values(this.#state.joins).filter(j => j !== join && j.ownerAlias === join.alias);
|
|
488
|
+
return children.flatMap(j => [j, ...this.getJoinSubtree(j)]);
|
|
489
|
+
}
|
|
490
|
+
condReferencesAlias(cond, alias) {
|
|
491
|
+
if (Array.isArray(cond)) {
|
|
492
|
+
return cond.some(c => this.condReferencesAlias(c, alias));
|
|
493
|
+
}
|
|
494
|
+
if (Utils.isPlainObject(cond)) {
|
|
495
|
+
return Object.entries(cond).some(([key, value]) => key.startsWith(`${alias}.`) || this.condReferencesAlias(value, alias));
|
|
496
|
+
}
|
|
497
|
+
return false;
|
|
498
|
+
}
|
|
459
499
|
withSubQuery(subQuery, alias) {
|
|
460
500
|
this.ensureNotFinalized();
|
|
461
501
|
if (isRaw(subQuery)) {
|
|
@@ -1104,7 +1144,9 @@ export class QueryBuilder {
|
|
|
1104
1144
|
}
|
|
1105
1145
|
if (stack.length > 0) {
|
|
1106
1146
|
const merged = this.driver.mergeJoinedResult(stack, this.mainAlias.meta, joinedProps);
|
|
1107
|
-
|
|
1147
|
+
for (const row of merged) {
|
|
1148
|
+
yield this.mapResult(row, options.mapResults);
|
|
1149
|
+
}
|
|
1108
1150
|
}
|
|
1109
1151
|
}
|
|
1110
1152
|
/**
|
|
@@ -1412,7 +1454,6 @@ export class QueryBuilder {
|
|
|
1412
1454
|
aliased: [QueryType.SELECT, QueryType.COUNT].includes(this.type),
|
|
1413
1455
|
});
|
|
1414
1456
|
const criteriaNode = CriteriaNodeFactory.createNode(this.metadata, prop.targetMeta.class, cond);
|
|
1415
|
-
const joinCountBefore = Object.keys(this.#state.joins).length;
|
|
1416
1457
|
cond = criteriaNode.process(this, { ignoreBranching: true, alias });
|
|
1417
1458
|
let aliasedName = `${fromAlias}.${prop.name}#${alias}`;
|
|
1418
1459
|
path ??= `${Object.values(this.#state.joins).find(j => j.alias === fromAlias)?.path ?? Utils.className(entityName)}.${prop.name}`;
|
|
@@ -1442,20 +1483,7 @@ export class QueryBuilder {
|
|
|
1442
1483
|
this.#state.joins[aliasedName] = this.helper.joinManyToOneReference(prop, ownerAlias, alias, type, cond, schema);
|
|
1443
1484
|
this.#state.joins[aliasedName].path ??= path;
|
|
1444
1485
|
}
|
|
1445
|
-
|
|
1446
|
-
// forward reference (the auto-join's ON refers to alias, while alias's ON refers back to it);
|
|
1447
|
-
// fold them into the new join so both aliases share scope in the outer ON clause (issue #7681)
|
|
1448
|
-
const condJoin = this.#state.joins[aliasedName];
|
|
1449
|
-
const joinKeys = Object.keys(this.#state.joins);
|
|
1450
|
-
for (let i = joinCountBefore; i < joinKeys.length; i++) {
|
|
1451
|
-
const j = this.#state.joins[joinKeys[i]];
|
|
1452
|
-
if (j === condJoin || j.ownerAlias !== alias) {
|
|
1453
|
-
continue;
|
|
1454
|
-
}
|
|
1455
|
-
const nested = (condJoin.nested ??= new Set());
|
|
1456
|
-
j.type = j.type === JoinType.innerJoin ? JoinType.nestedInnerJoin : JoinType.nestedLeftJoin;
|
|
1457
|
-
nested.add(j);
|
|
1458
|
-
}
|
|
1486
|
+
this.nestReferencedJoins(this.#state.joins[aliasedName]);
|
|
1459
1487
|
return { prop, key: aliasedName };
|
|
1460
1488
|
}
|
|
1461
1489
|
prepareFields(fields, type = 'where', schema) {
|
package/schema/SchemaHelper.d.ts
CHANGED
|
@@ -124,6 +124,8 @@ export declare abstract class SchemaHelper {
|
|
|
124
124
|
getAddColumnsSQL(table: DatabaseTable, columns: Column[]): string[];
|
|
125
125
|
getDropColumnsSQL(tableName: string, columns: Column[], schemaName?: string): string;
|
|
126
126
|
hasNonDefaultPrimaryKeyName(table: DatabaseTable): boolean;
|
|
127
|
+
/** Returns the `constraint <name> ` prefix for a primary key definition, empty when the server assigns the default name on its own. */
|
|
128
|
+
protected getPrimaryKeyConstraintPrefix(table: DatabaseTable, index: IndexDef): string;
|
|
127
129
|
castColumn(name: string, type: string): string;
|
|
128
130
|
alterTableColumn(column: Column, table: DatabaseTable, changedProperties: Set<string>): string[];
|
|
129
131
|
/** Returns the bare `collate <name>` clause for column DDL. Overridden by PostgreSQL to quote the identifier. */
|
package/schema/SchemaHelper.js
CHANGED
|
@@ -521,6 +521,10 @@ export class SchemaHelper {
|
|
|
521
521
|
const defaultName = this.platform.getDefaultPrimaryName(table.name, pkIndex.columnNames);
|
|
522
522
|
return pkIndex?.keyName !== defaultName;
|
|
523
523
|
}
|
|
524
|
+
/** Returns the `constraint <name> ` prefix for a primary key definition, empty when the server assigns the default name on its own. */
|
|
525
|
+
getPrimaryKeyConstraintPrefix(table, index) {
|
|
526
|
+
return this.hasNonDefaultPrimaryKeyName(table) ? `constraint ${this.quote(index.keyName)} ` : '';
|
|
527
|
+
}
|
|
524
528
|
/* v8 ignore next */
|
|
525
529
|
castColumn(name, type) {
|
|
526
530
|
return '';
|
|
@@ -766,7 +770,7 @@ export class SchemaHelper {
|
|
|
766
770
|
const primaryKey = table.getPrimaryKey();
|
|
767
771
|
const createPrimary = !table.getColumns().some(c => c.autoincrement && c.primary) || this.hasNonDefaultPrimaryKeyName(table);
|
|
768
772
|
if (createPrimary && primaryKey) {
|
|
769
|
-
const name = this.
|
|
773
|
+
const name = this.getPrimaryKeyConstraintPrefix(table, primaryKey);
|
|
770
774
|
sql += `, ${name}primary key (${primaryKey.columnNames.map(c => this.quote(c)).join(', ')})`;
|
|
771
775
|
}
|
|
772
776
|
sql += ')';
|
|
@@ -849,7 +853,7 @@ export class SchemaHelper {
|
|
|
849
853
|
const columns = index.columnNames.map(c => this.quote(c)).join(', ');
|
|
850
854
|
const defer = index.deferMode ? ` deferrable initially ${index.deferMode}` : '';
|
|
851
855
|
if (index.primary) {
|
|
852
|
-
const keyName = this.
|
|
856
|
+
const keyName = this.getPrimaryKeyConstraintPrefix(table, index);
|
|
853
857
|
return `alter table ${table.getQuotedName()} add ${keyName}primary key (${columns})${defer}`;
|
|
854
858
|
}
|
|
855
859
|
if (index.type === 'fulltext') {
|