@mikro-orm/sql 7.0.0-dev.191 → 7.0.0-dev.193
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 +2 -2
- package/query/QueryBuilder.d.ts +22 -1
- package/query/QueryBuilder.js +76 -11
- package/tsconfig.build.tsbuildinfo +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mikro-orm/sql",
|
|
3
|
-
"version": "7.0.0-dev.
|
|
3
|
+
"version": "7.0.0-dev.193",
|
|
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
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -56,6 +56,6 @@
|
|
|
56
56
|
"@mikro-orm/core": "^6.6.4"
|
|
57
57
|
},
|
|
58
58
|
"peerDependencies": {
|
|
59
|
-
"@mikro-orm/core": "7.0.0-dev.
|
|
59
|
+
"@mikro-orm/core": "7.0.0-dev.193"
|
|
60
60
|
}
|
|
61
61
|
}
|
package/query/QueryBuilder.d.ts
CHANGED
|
@@ -341,7 +341,28 @@ export declare class QueryBuilder<Entity extends object = AnyEntity, RootAlias e
|
|
|
341
341
|
private processNestedJoins;
|
|
342
342
|
private hasToManyJoins;
|
|
343
343
|
protected wrapPaginateSubQuery(meta: EntityMetadata): void;
|
|
344
|
-
|
|
344
|
+
/**
|
|
345
|
+
* Computes the set of populate paths from the _populate hints.
|
|
346
|
+
*/
|
|
347
|
+
protected getPopulatePaths(): Set<string>;
|
|
348
|
+
protected normalizeJoinPath(join: JoinOptions, meta: EntityMetadata): string;
|
|
349
|
+
/**
|
|
350
|
+
* Transfers WHERE conditions to ORDER BY joins that are not used for population.
|
|
351
|
+
* This ensures the outer query's ORDER BY uses the same filtered rows as the subquery.
|
|
352
|
+
* GH #6160
|
|
353
|
+
*/
|
|
354
|
+
protected transferConditionsForOrderByJoins(meta: EntityMetadata, cond: Dictionary | undefined, populatePaths: Set<string>): void;
|
|
355
|
+
/**
|
|
356
|
+
* Removes joins that are not used for population or ordering to improve performance.
|
|
357
|
+
*/
|
|
358
|
+
protected pruneJoinsForPagination(meta: EntityMetadata, populatePaths: Set<string>): void;
|
|
359
|
+
/**
|
|
360
|
+
* Transfers WHERE conditions that reference a join alias to the join's ON clause.
|
|
361
|
+
* This is needed when a join is kept for ORDER BY after pagination wrapping,
|
|
362
|
+
* so the outer query orders by the same filtered rows as the subquery.
|
|
363
|
+
* @internal
|
|
364
|
+
*/
|
|
365
|
+
protected transferConditionsToJoin(cond: Dictionary, join: JoinOptions, path?: string): void;
|
|
345
366
|
private wrapModifySubQuery;
|
|
346
367
|
private getSchema;
|
|
347
368
|
private createAlias;
|
package/query/QueryBuilder.js
CHANGED
|
@@ -1410,28 +1410,67 @@ export class QueryBuilder {
|
|
|
1410
1410
|
subSubQuery.select(pks).from(innerQuery);
|
|
1411
1411
|
this._limit = undefined;
|
|
1412
1412
|
this._offset = undefined;
|
|
1413
|
+
// Save the original WHERE conditions before pruning joins
|
|
1414
|
+
const originalCond = this._cond;
|
|
1415
|
+
const populatePaths = this.getPopulatePaths();
|
|
1413
1416
|
if (!this._fields.some(field => isRaw(field))) {
|
|
1414
|
-
this.
|
|
1417
|
+
this.pruneJoinsForPagination(meta, populatePaths);
|
|
1415
1418
|
}
|
|
1419
|
+
// Transfer WHERE conditions to ORDER BY joins (GH #6160)
|
|
1420
|
+
this.transferConditionsForOrderByJoins(meta, originalCond, populatePaths);
|
|
1416
1421
|
const { sql, params } = subSubQuery.compile();
|
|
1417
1422
|
this.select(this._fields).where({ [Utils.getPrimaryKeyHash(meta.primaryKeys)]: { $in: raw(sql, params) } });
|
|
1418
1423
|
}
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
.map(k => k.split('.')[0]);
|
|
1424
|
+
/**
|
|
1425
|
+
* Computes the set of populate paths from the _populate hints.
|
|
1426
|
+
*/
|
|
1427
|
+
getPopulatePaths() {
|
|
1428
|
+
const paths = new Set();
|
|
1425
1429
|
function addPath(hints, prefix = '') {
|
|
1426
1430
|
for (const hint of hints) {
|
|
1427
1431
|
const field = hint.field.split(':')[0];
|
|
1428
|
-
|
|
1432
|
+
const fullPath = prefix ? prefix + '.' + field : field;
|
|
1433
|
+
paths.add(fullPath);
|
|
1429
1434
|
if (hint.children) {
|
|
1430
|
-
addPath(hint.children,
|
|
1435
|
+
addPath(hint.children, fullPath);
|
|
1431
1436
|
}
|
|
1432
1437
|
}
|
|
1433
1438
|
}
|
|
1434
1439
|
addPath(this._populate);
|
|
1440
|
+
return paths;
|
|
1441
|
+
}
|
|
1442
|
+
normalizeJoinPath(join, meta) {
|
|
1443
|
+
return join.path?.replace(/\[populate]|\[pivot]|:ref/g, '').replace(new RegExp(`^${meta.className}.`), '') ?? '';
|
|
1444
|
+
}
|
|
1445
|
+
/**
|
|
1446
|
+
* Transfers WHERE conditions to ORDER BY joins that are not used for population.
|
|
1447
|
+
* This ensures the outer query's ORDER BY uses the same filtered rows as the subquery.
|
|
1448
|
+
* GH #6160
|
|
1449
|
+
*/
|
|
1450
|
+
transferConditionsForOrderByJoins(meta, cond, populatePaths) {
|
|
1451
|
+
if (!cond || this._orderBy.length === 0) {
|
|
1452
|
+
return;
|
|
1453
|
+
}
|
|
1454
|
+
const orderByAliases = new Set(this._orderBy
|
|
1455
|
+
.flatMap(hint => Object.keys(hint))
|
|
1456
|
+
.filter(k => !RawQueryFragment.isKnownFragmentSymbol(k))
|
|
1457
|
+
.map(k => k.split('.')[0]));
|
|
1458
|
+
for (const join of Object.values(this._joins)) {
|
|
1459
|
+
const joinPath = this.normalizeJoinPath(join, meta);
|
|
1460
|
+
const isPopulateJoin = populatePaths.has(joinPath);
|
|
1461
|
+
// Only transfer conditions for joins used for ORDER BY but not for population
|
|
1462
|
+
if (orderByAliases.has(join.alias) && !isPopulateJoin) {
|
|
1463
|
+
this.transferConditionsToJoin(cond, join);
|
|
1464
|
+
}
|
|
1465
|
+
}
|
|
1466
|
+
}
|
|
1467
|
+
/**
|
|
1468
|
+
* Removes joins that are not used for population or ordering to improve performance.
|
|
1469
|
+
*/
|
|
1470
|
+
pruneJoinsForPagination(meta, populatePaths) {
|
|
1471
|
+
const orderByAliases = this._orderBy
|
|
1472
|
+
.flatMap(hint => Object.keys(hint))
|
|
1473
|
+
.map(k => k.split('.')[0]);
|
|
1435
1474
|
const joins = Object.entries(this._joins);
|
|
1436
1475
|
const rootAlias = this.alias;
|
|
1437
1476
|
function addParentAlias(alias) {
|
|
@@ -1445,12 +1484,38 @@ export class QueryBuilder {
|
|
|
1445
1484
|
addParentAlias(orderByAlias);
|
|
1446
1485
|
}
|
|
1447
1486
|
for (const [key, join] of joins) {
|
|
1448
|
-
const path =
|
|
1449
|
-
if (!
|
|
1487
|
+
const path = this.normalizeJoinPath(join, meta);
|
|
1488
|
+
if (!populatePaths.has(path) && !orderByAliases.includes(join.alias)) {
|
|
1450
1489
|
delete this._joins[key];
|
|
1451
1490
|
}
|
|
1452
1491
|
}
|
|
1453
1492
|
}
|
|
1493
|
+
/**
|
|
1494
|
+
* Transfers WHERE conditions that reference a join alias to the join's ON clause.
|
|
1495
|
+
* This is needed when a join is kept for ORDER BY after pagination wrapping,
|
|
1496
|
+
* so the outer query orders by the same filtered rows as the subquery.
|
|
1497
|
+
* @internal
|
|
1498
|
+
*/
|
|
1499
|
+
transferConditionsToJoin(cond, join, path = '') {
|
|
1500
|
+
const aliasPrefix = join.alias + '.';
|
|
1501
|
+
for (const key of Object.keys(cond)) {
|
|
1502
|
+
const value = cond[key];
|
|
1503
|
+
// Handle $and/$or operators - recurse into nested conditions
|
|
1504
|
+
if (key === '$and' || key === '$or') {
|
|
1505
|
+
if (Array.isArray(value)) {
|
|
1506
|
+
for (const item of value) {
|
|
1507
|
+
this.transferConditionsToJoin(item, join, path);
|
|
1508
|
+
}
|
|
1509
|
+
}
|
|
1510
|
+
continue;
|
|
1511
|
+
}
|
|
1512
|
+
// Check if this condition references the join alias
|
|
1513
|
+
if (key.startsWith(aliasPrefix)) {
|
|
1514
|
+
// Add condition to the join's ON clause
|
|
1515
|
+
join.cond[key] = value;
|
|
1516
|
+
}
|
|
1517
|
+
}
|
|
1518
|
+
}
|
|
1454
1519
|
wrapModifySubQuery(meta) {
|
|
1455
1520
|
const subQuery = this.clone();
|
|
1456
1521
|
subQuery.finalized = true;
|