@mikro-orm/sql 7.2.0-dev.2 → 7.2.0-dev.20

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.
Files changed (46) hide show
  1. package/AbstractSqlConnection.d.ts +30 -3
  2. package/AbstractSqlConnection.js +75 -15
  3. package/AbstractSqlDriver.d.ts +1 -8
  4. package/AbstractSqlDriver.js +124 -55
  5. package/AbstractSqlPlatform.d.ts +4 -2
  6. package/AbstractSqlPlatform.js +35 -1
  7. package/SqlEntityManager.d.ts +2 -2
  8. package/SqlEntityManager.js +5 -4
  9. package/dialects/mssql/MsSqlNativeQueryBuilder.js +1 -1
  10. package/dialects/mysql/BaseMySqlPlatform.d.ts +2 -0
  11. package/dialects/mysql/BaseMySqlPlatform.js +4 -0
  12. package/dialects/mysql/MySqlSchemaHelper.d.ts +1 -0
  13. package/dialects/mysql/MySqlSchemaHelper.js +4 -1
  14. package/dialects/oracledb/OracleNativeQueryBuilder.js +1 -1
  15. package/dialects/postgresql/BasePostgreSqlPlatform.d.ts +3 -1
  16. package/dialects/postgresql/BasePostgreSqlPlatform.js +35 -2
  17. package/dialects/postgresql/PostgreSqlExceptionConverter.js +8 -1
  18. package/dialects/postgresql/PostgreSqlSchemaHelper.d.ts +22 -1
  19. package/dialects/postgresql/PostgreSqlSchemaHelper.js +181 -4
  20. package/dialects/sqlite/BaseSqliteConnection.d.ts +3 -0
  21. package/dialects/sqlite/BaseSqliteConnection.js +15 -5
  22. package/dialects/sqlite/SqlitePlatform.d.ts +2 -0
  23. package/dialects/sqlite/SqlitePlatform.js +4 -0
  24. package/dialects/sqlite/SqliteSchemaHelper.js +2 -2
  25. package/package.json +4 -4
  26. package/plugin/transformer.d.ts +7 -1
  27. package/plugin/transformer.js +60 -1
  28. package/query/CriteriaNodeFactory.js +4 -0
  29. package/query/NativeQueryBuilder.js +1 -1
  30. package/query/ObjectCriteriaNode.d.ts +1 -0
  31. package/query/ObjectCriteriaNode.js +30 -5
  32. package/query/QueryBuilder.d.ts +40 -7
  33. package/query/QueryBuilder.js +181 -37
  34. package/query/QueryBuilderHelper.d.ts +5 -0
  35. package/query/QueryBuilderHelper.js +39 -9
  36. package/schema/DatabaseSchema.d.ts +4 -0
  37. package/schema/DatabaseSchema.js +107 -1
  38. package/schema/DatabaseTable.d.ts +15 -1
  39. package/schema/DatabaseTable.js +113 -19
  40. package/schema/SchemaComparator.d.ts +3 -0
  41. package/schema/SchemaComparator.js +123 -10
  42. package/schema/SchemaHelper.d.ts +26 -1
  43. package/schema/SchemaHelper.js +67 -9
  44. package/schema/SqlSchemaGenerator.d.ts +4 -0
  45. package/schema/SqlSchemaGenerator.js +59 -22
  46. package/typings.d.ts +20 -2
@@ -6,6 +6,8 @@ import { SqliteExceptionConverter } from './SqliteExceptionConverter.js';
6
6
  export declare class SqlitePlatform extends AbstractSqlPlatform {
7
7
  protected readonly schemaHelper: SqliteSchemaHelper;
8
8
  protected readonly exceptionConverter: SqliteExceptionConverter;
9
+ /** sqlite treats null as the lowest value when no placement is requested. */
10
+ sortsNullsLowest(): boolean;
9
11
  /** @internal */
10
12
  createNativeQueryBuilder(): SqliteNativeQueryBuilder;
11
13
  usesDefaultKeyword(): boolean;
@@ -5,6 +5,10 @@ import { SqliteExceptionConverter } from './SqliteExceptionConverter.js';
5
5
  export class SqlitePlatform extends AbstractSqlPlatform {
6
6
  schemaHelper = new SqliteSchemaHelper(this);
7
7
  exceptionConverter = new SqliteExceptionConverter();
8
+ /** sqlite treats null as the lowest value when no placement is requested. */
9
+ sortsNullsLowest() {
10
+ return true;
11
+ }
8
12
  /** @internal */
9
13
  createNativeQueryBuilder() {
10
14
  return new SqliteNativeQueryBuilder(this);
@@ -489,7 +489,7 @@ export class SqliteSchemaHelper extends SchemaHelper {
489
489
  * Foreign key references can only point to tables in the same database.
490
490
  */
491
491
  getReferencedTableName(referencedTableName, schema) {
492
- const [schemaName, tableName] = this.splitTableName(referencedTableName);
492
+ const [, tableName] = this.splitTableName(referencedTableName);
493
493
  // Strip any schema prefix - SQLite REFERENCES clause doesn't support it
494
494
  return tableName;
495
495
  }
@@ -561,7 +561,7 @@ export class SqliteSchemaHelper extends SchemaHelper {
561
561
  for (const event of trigger.events) {
562
562
  const name = trigger.events.length > 1 ? `${trigger.name}_${event}` : trigger.name;
563
563
  const when = trigger.when ? `\n when ${trigger.when}` : '';
564
- ret.push(`create trigger ${this.quote(name)} ${timing} ${event.toUpperCase()} on ${table.getQuotedName()} for each ${forEach}${when} begin ${trigger.body}; end`);
564
+ ret.push(`create trigger ${this.quote(name)} ${timing} ${event.toUpperCase()} on ${table.getQuotedName()} for each ${forEach}${when} begin ${this.normalizeTriggerBody(trigger.body)} end`);
565
565
  }
566
566
  return ret.join(';\n');
567
567
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mikro-orm/sql",
3
- "version": "7.2.0-dev.2",
3
+ "version": "7.2.0-dev.20",
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",
@@ -47,13 +47,13 @@
47
47
  "copy": "node ../../scripts/copy.mjs"
48
48
  },
49
49
  "dependencies": {
50
- "kysely": "0.29.4"
50
+ "kysely": "0.29.5"
51
51
  },
52
52
  "devDependencies": {
53
- "@mikro-orm/core": "^7.1.7"
53
+ "@mikro-orm/core": "^7.1.15"
54
54
  },
55
55
  "peerDependencies": {
56
- "@mikro-orm/core": "7.2.0-dev.2"
56
+ "@mikro-orm/core": "7.2.0-dev.20"
57
57
  },
58
58
  "engines": {
59
59
  "node": ">= 22.17.0"
@@ -1,5 +1,5 @@
1
1
  import { type EntityMetadata, type EntityProperty, type Type } from '@mikro-orm/core';
2
- import { type CommonTableExpressionNameNode, type DeleteQueryNode, type InsertQueryNode, type JoinNode, type MergeQueryNode, type OperationNode, type QueryId, type SelectQueryNode, type UpdateQueryNode, type WithNode, ColumnNode, IdentifierNode, OperationNodeTransformer, SelectionNode, TableNode, ValueNode } from 'kysely';
2
+ import { type BinaryOperationNode, type CommonTableExpressionNameNode, type DeleteQueryNode, type InsertQueryNode, type JoinNode, type MergeQueryNode, type OperationNode, type QueryId, type SelectQueryNode, type UpdateQueryNode, type WithNode, ColumnNode, IdentifierNode, OperationNodeTransformer, SelectionNode, TableNode, ValueNode } from 'kysely';
3
3
  import type { MikroKyselyPluginOptions } from './index.js';
4
4
  import type { SqlEntityManager } from '../SqlEntityManager.js';
5
5
  export declare class MikroTransformer extends OperationNodeTransformer {
@@ -28,6 +28,12 @@ export declare class MikroTransformer extends OperationNodeTransformer {
28
28
  processOnUpdateHooks(node: UpdateQueryNode, meta: EntityMetadata): UpdateQueryNode;
29
29
  processInsertValues(node: InsertQueryNode, meta: EntityMetadata): InsertQueryNode;
30
30
  processUpdateValues(node: UpdateQueryNode, meta: EntityMetadata): UpdateQueryNode;
31
+ transformBinaryOperation(node: BinaryOperationNode, queryId: QueryId): BinaryOperationNode;
32
+ /** Resolve the entity property a comparison's left operand refers to, so its value operand can be converted. */
33
+ resolveOperandProperty(operand: OperationNode): {
34
+ prop: EntityProperty;
35
+ fieldName: string;
36
+ } | undefined;
31
37
  processInputValueNode(prop: EntityProperty | undefined, fieldName: string | undefined, valueNode: ValueNode): OperationNode;
32
38
  expandSelections(selections: readonly SelectionNode[]): readonly SelectionNode[];
33
39
  expandSelection(sel: SelectionNode): SelectionNode[] | null;
@@ -455,6 +455,60 @@ export class MikroTransformer extends OperationNodeTransformer {
455
455
  updates,
456
456
  };
457
457
  }
458
+ transformBinaryOperation(node, queryId) {
459
+ const transformed = super.transformBinaryOperation(node, queryId);
460
+ if (!this.#options.convertValues) {
461
+ return transformed;
462
+ }
463
+ const resolved = this.resolveOperandProperty(transformed.leftOperand);
464
+ if (!resolved) {
465
+ return transformed;
466
+ }
467
+ const { prop, fieldName } = resolved;
468
+ const right = transformed.rightOperand;
469
+ if (ValueNode.is(right)) {
470
+ const converted = this.processInputValueNode(prop, fieldName, right);
471
+ return converted === right ? transformed : { ...transformed, rightOperand: converted };
472
+ }
473
+ if (PrimitiveValueListNode.is(right)) {
474
+ // upgrade to ValueListNode when the type needs SQL-side wrapping, since
475
+ // PrimitiveValueListNode can only hold primitives
476
+ if (prop.hasConvertToDatabaseValueSQL) {
477
+ const values = right.values.map(value => this.processInputValueNode(prop, fieldName, ValueNode.create(value)));
478
+ return { ...transformed, rightOperand: ValueListNode.create(values) };
479
+ }
480
+ const values = right.values.map(value => this.prepareInputValue(prop, value, true));
481
+ return values.every((value, idx) => value === right.values[idx])
482
+ ? transformed
483
+ : { ...transformed, rightOperand: PrimitiveValueListNode.create(values) };
484
+ }
485
+ if (ValueListNode.is(right)) {
486
+ let changed = false;
487
+ const values = right.values.map(valueNode => {
488
+ if (!ValueNode.is(valueNode)) {
489
+ return valueNode;
490
+ }
491
+ const converted = this.processInputValueNode(prop, fieldName, valueNode);
492
+ if (converted !== valueNode) {
493
+ changed = true;
494
+ }
495
+ return converted;
496
+ });
497
+ return changed ? { ...transformed, rightOperand: ValueListNode.create(values) } : transformed;
498
+ }
499
+ return transformed;
500
+ }
501
+ /** Resolve the entity property a comparison's left operand refers to, so its value operand can be converted. */
502
+ resolveOperandProperty(operand) {
503
+ if (!ReferenceNode.is(operand) || !ColumnNode.is(operand.column)) {
504
+ return undefined;
505
+ }
506
+ const tableName = operand.table ? this.getTableName(operand.table) : undefined;
507
+ const meta = this.findOwnerMeta(tableName);
508
+ const fieldName = this.normalizeColumnName(operand.column.column);
509
+ const prop = this.findProperty(meta, fieldName);
510
+ return prop ? { prop, fieldName } : undefined;
511
+ }
458
512
  processInputValueNode(prop, fieldName, valueNode) {
459
513
  const converted = this.prepareInputValue(prop, valueNode.value, true);
460
514
  const newValueNode = converted === valueNode.value
@@ -543,8 +597,13 @@ export class MikroTransformer extends OperationNodeTransformer {
543
597
  if (name) {
544
598
  return this.lookupInContextStack(name) ?? this.#subqueryAliasMap.get(name) ?? this.findEntityMetadata(name);
545
599
  }
600
+ // the stack can be empty when transforming a raw root node with embedded expressions
601
+ const context = this.#contextStack[this.#contextStack.length - 1];
602
+ if (!context) {
603
+ return undefined;
604
+ }
546
605
  let single;
547
- for (const meta of this.#contextStack[this.#contextStack.length - 1].values()) {
606
+ for (const meta of context.values()) {
548
607
  if (!meta) {
549
608
  continue;
550
609
  }
@@ -57,6 +57,10 @@ export class CriteriaNodeFactory {
57
57
  if (isNotEmbedded && prop?.customType instanceof JsonType) {
58
58
  return this.createScalarNode(metadata, childEntity, val, node, key, validate);
59
59
  }
60
+ // operator payloads under an unresolvable alias-prefixed key (e.g. `a.meta`) are opaque values, not entity criteria
61
+ if (!prop && !rawField && Utils.isOperator(key, false) && String(node.key).includes('.')) {
62
+ return this.createScalarNode(metadata, entityName, val, node, key, validate);
63
+ }
60
64
  if (prop?.kind === ReferenceKind.SCALAR && val != null && Object.keys(val).some(f => f in GroupOperator)) {
61
65
  throw ValidationError.cannotUseGroupOperatorsInsideScalars(entityName, prop.name, payload);
62
66
  }
@@ -308,7 +308,7 @@ export class NativeQueryBuilder {
308
308
  const fields = this.options.groupBy.map(field => this.quote(field));
309
309
  this.parts.push(`group by ${fields.join(', ')}`);
310
310
  }
311
- if (this.options.having) {
311
+ if (this.options.having?.sql.trim()) {
312
312
  this.parts.push(`having ${this.options.having.sql}`);
313
313
  this.params.push(...this.options.having.params);
314
314
  }
@@ -13,6 +13,7 @@ export declare class ObjectCriteriaNode<T extends object> extends CriteriaNode<T
13
13
  private inlineArrayChildPayload;
14
14
  private inlineChildPayload;
15
15
  private inlineCondition;
16
+ private isCollectionOperator;
16
17
  private shouldAutoJoin;
17
18
  private autoJoin;
18
19
  private isPrefixed;
@@ -1,7 +1,7 @@
1
1
  import { ALIAS_REPLACEMENT, GroupOperator, QueryFlag, raw, RawQueryFragment, ReferenceKind, Utils, } from '@mikro-orm/core';
2
2
  import { CriteriaNode } from './CriteriaNode.js';
3
3
  import { JoinType, QueryType } from './enums.js';
4
- const COLLECTION_OPERATORS = ['$some', '$none', '$every', '$size'];
4
+ const COLLECTION_OPERATORS = ['$some', '$none', '$every', '$size', '$all'];
5
5
  /**
6
6
  * @internal
7
7
  */
@@ -17,7 +17,7 @@ export class ObjectCriteriaNode extends CriteriaNode {
17
17
  alias = nestedAlias;
18
18
  }
19
19
  if (this.shouldAutoJoin(qb, nestedAlias)) {
20
- if (keys.some(k => COLLECTION_OPERATORS.includes(k))) {
20
+ if (keys.some(k => this.isCollectionOperator(k))) {
21
21
  if (![ReferenceKind.MANY_TO_MANY, ReferenceKind.ONE_TO_MANY].includes(this.prop.kind)) {
22
22
  // ignore collection operators when used on a non-relational property - this can happen when they get into
23
23
  // populateWhere via `infer` on m:n properties with select-in strategy
@@ -34,11 +34,24 @@ export class ObjectCriteriaNode extends CriteriaNode {
34
34
  const primaryKeys = parentMeta.primaryKeys.map(pk => {
35
35
  return [QueryType.SELECT, QueryType.COUNT].includes(qb.type) ? `${knownKey ? alias : ownerAlias}.${pk}` : pk;
36
36
  });
37
+ const conditions = [];
38
+ let matchNothing = false;
37
39
  for (const key of keys) {
38
40
  if (typeof key !== 'string' || !COLLECTION_OPERATORS.includes(key)) {
39
41
  throw new Error('Mixing collection operators with other filters is not allowed.');
40
42
  }
41
43
  const payload = this.payload[key].unwrap();
44
+ // `$all` requires every listed item to be present, which is an intersection of `$some` conditions
45
+ if (key === '$all') {
46
+ // an empty `$all` matches nothing, same as in mongo
47
+ matchNothing ||= payload.length === 0;
48
+ conditions.push(...payload.map(item => ['$some', item]));
49
+ }
50
+ else {
51
+ conditions.push([key, payload]);
52
+ }
53
+ }
54
+ for (const [key, payload] of conditions) {
42
55
  // entities with a fixed schema must resolve the `from` table's own schema in the subquery,
43
56
  // otherwise a nested operator inherits the root entity's schema (GH #7894); for wildcard or
44
57
  // schema-less entities the schema is resolved dynamically and needs to be carried over
@@ -70,6 +83,9 @@ export class ObjectCriteriaNode extends CriteriaNode {
70
83
  [Utils.getPrimaryKeyHash(primaryKeys)]: { [op]: sub.getNativeQuery().toRaw() },
71
84
  });
72
85
  }
86
+ if (matchNothing) {
87
+ $and.push({ [Utils.getPrimaryKeyHash(primaryKeys)]: { $in: [] } });
88
+ }
73
89
  if ($and.length === 1) {
74
90
  return $and[0];
75
91
  }
@@ -113,7 +129,8 @@ export class ObjectCriteriaNode extends CriteriaNode {
113
129
  }
114
130
  else if (isRawField) {
115
131
  const rawField = RawQueryFragment.getKnownFragment(field);
116
- o[raw(rawField.sql.replaceAll(ALIAS_REPLACEMENT, alias), rawField.params)] = payload;
132
+ qb.ensureTPTJoins();
133
+ o[raw(qb.helper.replaceAliases(rawField.sql, alias), rawField.params)] = payload;
117
134
  }
118
135
  else if (!childNode.validate && !childNode.prop && !field.includes('.') && !operator) {
119
136
  // wrap unknown fields in raw() to prevent alias prefixing (e.g. raw SQL aliases in HAVING)
@@ -153,7 +170,7 @@ export class ObjectCriteriaNode extends CriteriaNode {
153
170
  alias = nestedAlias;
154
171
  }
155
172
  if (this.shouldAutoJoin(qb, nestedAlias)) {
156
- return !keys.some(k => COLLECTION_OPERATORS.includes(k));
173
+ return !keys.some(k => this.isCollectionOperator(k));
157
174
  }
158
175
  return keys.some(field => {
159
176
  const childNode = this.payload[field];
@@ -237,6 +254,14 @@ export class ObjectCriteriaNode extends CriteriaNode {
237
254
  delete o[key];
238
255
  o.$and = $and;
239
256
  }
257
+ isCollectionOperator(key) {
258
+ if (typeof key !== 'string' || !COLLECTION_OPERATORS.includes(key)) {
259
+ return false;
260
+ }
261
+ // `$all` is primarily a mongo array operator, in SQL it is supported only on collections
262
+ return (key !== '$all' ||
263
+ (!!this.prop && [ReferenceKind.MANY_TO_MANY, ReferenceKind.ONE_TO_MANY].includes(this.prop.kind)));
264
+ }
240
265
  shouldAutoJoin(qb, nestedAlias) {
241
266
  if (!this.prop || !this.parent) {
242
267
  return false;
@@ -245,7 +270,7 @@ export class ObjectCriteriaNode extends CriteriaNode {
245
270
  if (keys.every(k => typeof k === 'string' && k.includes('.') && k.startsWith(`${qb.alias}.`))) {
246
271
  return false;
247
272
  }
248
- if (keys.some(k => COLLECTION_OPERATORS.includes(k))) {
273
+ if (keys.some(k => this.isCollectionOperator(k))) {
249
274
  return true;
250
275
  }
251
276
  const meta = this.metadata.find(this.entityName);
@@ -152,11 +152,10 @@ type ContextFilterKeys<Context> = {
152
152
  type RawFilterKeys<RawAliases extends string> = {
153
153
  [K in RawAliases]?: AliasedFilterValue;
154
154
  };
155
- type NestedFilterCondition<Entity, RootAlias extends string, Context, RawAliases extends string> = ObjectQuery<Entity> & (IsNever<RootAlias> extends true ? {} : string extends RootAlias ? {} : RootAliasFilterKeys<RootAlias, Entity>) & ([Context] extends [never] ? {} : ContextFilterKeys<Context>) & (IsNever<RawAliases> extends true ? {} : string extends RawAliases ? {} : RawFilterKeys<RawAliases>);
156
155
  type GroupOperators<RootAlias extends string, Context, Entity, RawAliases extends string> = {
157
- $and?: NestedFilterCondition<Entity, RootAlias, Context, RawAliases>[];
158
- $or?: NestedFilterCondition<Entity, RootAlias, Context, RawAliases>[];
159
- $not?: NestedFilterCondition<Entity, RootAlias, Context, RawAliases>;
156
+ $and?: QBFilterQuery<Entity, RootAlias, Context, RawAliases>[];
157
+ $or?: QBFilterQuery<Entity, RootAlias, Context, RawAliases>[];
158
+ $not?: QBFilterQuery<Entity, RootAlias, Context, RawAliases>;
160
159
  };
161
160
  export type AliasedFilterCondition<RootAlias extends string, Context, Entity, RawAliases extends string = never> = (IsNever<RootAlias> extends true ? {} : string extends RootAlias ? {} : RootAliasFilterKeys<RootAlias, Entity>) & ([Context] extends [never] ? {} : ContextFilterKeys<Context>) & (IsNever<RawAliases> extends true ? {} : string extends RawAliases ? {} : RawFilterKeys<RawAliases>) & GroupOperators<RootAlias, Context, Entity, RawAliases>;
162
161
  export type QBFilterQuery<Entity, RootAlias extends string = never, Context = never, RawAliases extends string = never> = FilterObject<Entity> & AliasedFilterCondition<RootAlias, Context, Entity, RawAliases>;
@@ -440,7 +439,7 @@ export declare class QueryBuilder<Entity extends object = AnyEntity, RootAlias e
440
439
  */
441
440
  join<Field extends QBField<Entity, RootAlias, Context>, Alias extends string>(field: Field, alias: Alias, cond?: JoinCondition<JoinedEntityType<Entity, Context, Field & string>, Alias>, type?: JoinType, path?: string, schema?: string): SelectQueryBuilder<Entity, RootAlias, ModifyHint<RootAlias, Context, Hint, Field> & {}, ModifyContext<Entity, Context, Field, Alias>, RawAliases, Fields, CTEs>;
442
441
  /**
443
- * Adds a JOIN clause to the query for a subquery.
442
+ * Adds a JOIN clause to the query for a subquery. Use `sql.ref('...')` to join a table or CTE by name.
444
443
  */
445
444
  join<Alias extends string>(field: RawQueryFragment | QueryBuilder<any>, alias: Alias, cond?: RawJoinCondition, type?: JoinType, path?: string, schema?: string): SelectQueryBuilder<Entity, RootAlias, Hint, ModifyContext<Entity, Context, string, Alias>, RawAliases, Fields, CTEs>;
446
445
  /**
@@ -448,7 +447,7 @@ export declare class QueryBuilder<Entity extends object = AnyEntity, RootAlias e
448
447
  */
449
448
  innerJoin<Field extends QBField<Entity, RootAlias, Context>, Alias extends string>(field: Field, alias: Alias, cond?: JoinCondition<JoinedEntityType<Entity, Context, Field & string>, Alias>, schema?: string): SelectQueryBuilder<Entity, RootAlias, ModifyHint<RootAlias, Context, Hint, Field> & {}, ModifyContext<Entity, Context, Field, Alias>, RawAliases, Fields, CTEs>;
450
449
  /**
451
- * Adds an INNER JOIN clause to the query for a subquery.
450
+ * Adds an INNER JOIN clause to the query for a subquery. Use `sql.ref('...')` to join a table or CTE by name.
452
451
  */
453
452
  innerJoin<Alias extends string>(field: RawQueryFragment | QueryBuilder<any>, alias: Alias, cond?: RawJoinCondition, schema?: string): SelectQueryBuilder<Entity, RootAlias, Hint, ModifyContext<Entity, Context, string, Alias>, RawAliases, Fields, CTEs>;
454
453
  innerJoinLateral<Alias extends string>(field: RawQueryFragment | QueryBuilder<any>, alias: Alias, cond?: RawJoinCondition, schema?: string): SelectQueryBuilder<Entity, RootAlias, Hint, ModifyContext<Entity, Context, string, Alias>, RawAliases, Fields, CTEs>;
@@ -457,7 +456,7 @@ export declare class QueryBuilder<Entity extends object = AnyEntity, RootAlias e
457
456
  */
458
457
  leftJoin<Field extends QBField<Entity, RootAlias, Context>, Alias extends string>(field: Field, alias: Alias, cond?: JoinCondition<JoinedEntityType<Entity, Context, Field & string>, Alias>, schema?: string): SelectQueryBuilder<Entity, RootAlias, ModifyHint<RootAlias, Context, Hint, Field> & {}, ModifyContext<Entity, Context, Field, Alias>, RawAliases, Fields, CTEs>;
459
458
  /**
460
- * Adds a LEFT JOIN clause to the query for a subquery.
459
+ * Adds a LEFT JOIN clause to the query for a subquery. Use `sql.ref('...')` to join a table or CTE by name.
461
460
  */
462
461
  leftJoin<Alias extends string>(field: RawQueryFragment | QueryBuilder<any>, alias: Alias, cond?: RawJoinCondition, schema?: string): SelectQueryBuilder<Entity, RootAlias, Hint, ModifyContext<Entity, Context, string, Alias>, RawAliases, Fields, CTEs>;
463
462
  leftJoinLateral<Alias extends string>(field: RawQueryFragment | QueryBuilder<any>, alias: Alias, cond?: RawJoinCondition, schema?: string): SelectQueryBuilder<Entity, RootAlias, Hint, ModifyContext<Entity, Context, string, Alias>, RawAliases, Fields, CTEs>;
@@ -506,6 +505,17 @@ export declare class QueryBuilder<Entity extends object = AnyEntity, RootAlias e
506
505
  * @internal
507
506
  */
508
507
  applyJoinedFilters(em: EntityManager, filterOptions: FilterOptions | undefined): Promise<void>;
508
+ /**
509
+ * The `on` clause of `condJoin` — its explicit join condition or a filter condition merged into
510
+ * it — can reference the alias of any join in its subtree, both auto-joins created while
511
+ * processing the condition and pre-existing joined paths, all of which render after `condJoin`
512
+ * and would be forward alias references (issues #7681, #8090, #8099). When that happens, fold the
513
+ * subtree into `condJoin`, so it renders as a single parenthesized join group and every alias
514
+ * shares the scope of the outer `on` clause.
515
+ */
516
+ private nestReferencedJoins;
517
+ private getJoinSubtree;
518
+ private condReferencesAlias;
509
519
  withSubQuery(subQuery: RawQueryFragment | NativeQueryBuilder, alias: string): this;
510
520
  /**
511
521
  * Adds a WHERE clause to the query using an object condition.
@@ -772,6 +782,8 @@ export declare class QueryBuilder<Entity extends object = AnyEntity, RootAlias e
772
782
  * @internal
773
783
  */
774
784
  getJoinForPath(path: string, options?: ICriteriaNodeProcessOptions): JoinOptions | undefined;
785
+ /** Branch-insensitive path matching still must not cross branches — segments with different explicit branch markers (e.g. `Mobile[0]` vs `Mobile[1]`) belong to sibling joins. */
786
+ private branchesConflict;
775
787
  /**
776
788
  * @internal
777
789
  */
@@ -938,6 +950,13 @@ export declare class QueryBuilder<Entity extends object = AnyEntity, RootAlias e
938
950
  */
939
951
  addPropertyJoin(prop: EntityProperty, ownerAlias: string, alias: string, type: JoinType, path: string, schema?: string): string;
940
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;
941
960
  protected prepareFields<T>(fields: InternalField<T>[], type?: 'where' | 'groupBy' | 'sub-query', schema?: string): (string | RawQueryFragment)[];
942
961
  /**
943
962
  * Resolves nested paths like `a.books.title` to their actual field references.
@@ -984,6 +1003,20 @@ export declare class QueryBuilder<Entity extends object = AnyEntity, RootAlias e
984
1003
  processPopulateHint(): void;
985
1004
  private processPopulateWhere;
986
1005
  private mergeOnConditions;
1006
+ /**
1007
+ * `$or` branches can be moved to a join's `on` clause only when they all target that same join
1008
+ * and stay flat — a partial `$or` in the `on` clause would drop rows matching a sibling branch,
1009
+ * and nested operators cannot be preserved inside a distributed disjunction, as `mergeOnConditions`
1010
+ * would flatten them into `and` conjuncts.
1011
+ */
1012
+ private canDistributeOrBranches;
1013
+ private getOrBranchAliases;
1014
+ /**
1015
+ * An entity filter's `$or` that cannot be distributed is applied intact to the `on` clause of the
1016
+ * outermost join common to all targeted joins, nesting the targeted joins under it so the clause
1017
+ * can reference their aliases.
1018
+ */
1019
+ private mergeFilterOrCondition;
987
1020
  /**
988
1021
  * When adding an inner join on a left joined relation, we need to nest them,
989
1022
  * otherwise the inner join could discard rows of the root table.