@mikro-orm/sql 7.0.2 → 7.0.3-dev.0

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 (86) hide show
  1. package/AbstractSqlConnection.d.ts +58 -94
  2. package/AbstractSqlConnection.js +238 -235
  3. package/AbstractSqlDriver.d.ts +155 -411
  4. package/AbstractSqlDriver.js +1937 -2061
  5. package/AbstractSqlPlatform.d.ts +73 -83
  6. package/AbstractSqlPlatform.js +158 -162
  7. package/PivotCollectionPersister.d.ts +15 -33
  8. package/PivotCollectionPersister.js +160 -158
  9. package/SqlEntityManager.d.ts +22 -67
  10. package/SqlEntityManager.js +38 -54
  11. package/SqlEntityRepository.d.ts +14 -14
  12. package/SqlEntityRepository.js +23 -23
  13. package/dialects/mssql/MsSqlNativeQueryBuilder.d.ts +12 -12
  14. package/dialects/mssql/MsSqlNativeQueryBuilder.js +194 -192
  15. package/dialects/mysql/BaseMySqlPlatform.d.ts +45 -64
  16. package/dialects/mysql/BaseMySqlPlatform.js +131 -134
  17. package/dialects/mysql/MySqlExceptionConverter.d.ts +6 -6
  18. package/dialects/mysql/MySqlExceptionConverter.js +77 -91
  19. package/dialects/mysql/MySqlNativeQueryBuilder.d.ts +3 -3
  20. package/dialects/mysql/MySqlNativeQueryBuilder.js +69 -66
  21. package/dialects/mysql/MySqlSchemaHelper.d.ts +39 -39
  22. package/dialects/mysql/MySqlSchemaHelper.js +319 -327
  23. package/dialects/oracledb/OracleDialect.d.ts +52 -81
  24. package/dialects/oracledb/OracleDialect.js +149 -155
  25. package/dialects/oracledb/OracleNativeQueryBuilder.d.ts +12 -12
  26. package/dialects/oracledb/OracleNativeQueryBuilder.js +236 -232
  27. package/dialects/postgresql/BasePostgreSqlPlatform.d.ts +105 -108
  28. package/dialects/postgresql/BasePostgreSqlPlatform.js +350 -351
  29. package/dialects/postgresql/FullTextType.d.ts +6 -10
  30. package/dialects/postgresql/FullTextType.js +51 -51
  31. package/dialects/postgresql/PostgreSqlExceptionConverter.d.ts +5 -5
  32. package/dialects/postgresql/PostgreSqlExceptionConverter.js +43 -55
  33. package/dialects/postgresql/PostgreSqlNativeQueryBuilder.d.ts +1 -1
  34. package/dialects/postgresql/PostgreSqlNativeQueryBuilder.js +4 -4
  35. package/dialects/postgresql/PostgreSqlSchemaHelper.d.ts +82 -102
  36. package/dialects/postgresql/PostgreSqlSchemaHelper.js +683 -711
  37. package/dialects/sqlite/BaseSqliteConnection.d.ts +5 -3
  38. package/dialects/sqlite/BaseSqliteConnection.js +19 -21
  39. package/dialects/sqlite/NodeSqliteDialect.d.ts +1 -1
  40. package/dialects/sqlite/NodeSqliteDialect.js +23 -23
  41. package/dialects/sqlite/SqliteDriver.d.ts +1 -1
  42. package/dialects/sqlite/SqliteDriver.js +3 -3
  43. package/dialects/sqlite/SqliteExceptionConverter.d.ts +6 -6
  44. package/dialects/sqlite/SqliteExceptionConverter.js +51 -67
  45. package/dialects/sqlite/SqliteNativeQueryBuilder.d.ts +2 -2
  46. package/dialects/sqlite/SqliteNativeQueryBuilder.js +7 -7
  47. package/dialects/sqlite/SqlitePlatform.d.ts +72 -63
  48. package/dialects/sqlite/SqlitePlatform.js +139 -139
  49. package/dialects/sqlite/SqliteSchemaHelper.d.ts +60 -70
  50. package/dialects/sqlite/SqliteSchemaHelper.js +520 -533
  51. package/package.json +2 -2
  52. package/plugin/index.d.ts +35 -42
  53. package/plugin/index.js +36 -43
  54. package/plugin/transformer.d.ts +94 -117
  55. package/plugin/transformer.js +881 -890
  56. package/query/ArrayCriteriaNode.d.ts +4 -4
  57. package/query/ArrayCriteriaNode.js +18 -18
  58. package/query/CriteriaNode.d.ts +25 -35
  59. package/query/CriteriaNode.js +123 -133
  60. package/query/CriteriaNodeFactory.d.ts +6 -49
  61. package/query/CriteriaNodeFactory.js +94 -97
  62. package/query/NativeQueryBuilder.d.ts +118 -118
  63. package/query/NativeQueryBuilder.js +480 -484
  64. package/query/ObjectCriteriaNode.d.ts +12 -12
  65. package/query/ObjectCriteriaNode.js +282 -298
  66. package/query/QueryBuilder.d.ts +904 -1546
  67. package/query/QueryBuilder.js +2145 -2270
  68. package/query/QueryBuilderHelper.d.ts +72 -153
  69. package/query/QueryBuilderHelper.js +1028 -1079
  70. package/query/ScalarCriteriaNode.d.ts +3 -3
  71. package/query/ScalarCriteriaNode.js +46 -53
  72. package/query/enums.d.ts +14 -14
  73. package/query/enums.js +14 -14
  74. package/query/raw.d.ts +6 -16
  75. package/query/raw.js +10 -10
  76. package/schema/DatabaseSchema.d.ts +50 -73
  77. package/schema/DatabaseSchema.js +307 -331
  78. package/schema/DatabaseTable.d.ts +73 -96
  79. package/schema/DatabaseTable.js +927 -1012
  80. package/schema/SchemaComparator.d.ts +54 -58
  81. package/schema/SchemaComparator.js +719 -745
  82. package/schema/SchemaHelper.d.ts +95 -109
  83. package/schema/SchemaHelper.js +659 -675
  84. package/schema/SqlSchemaGenerator.d.ts +58 -78
  85. package/schema/SqlSchemaGenerator.js +501 -535
  86. package/typings.d.ts +266 -380
@@ -1,22 +1,5 @@
1
1
  var _a;
2
- import {
3
- EntityMetadata,
4
- helper,
5
- inspect,
6
- isRaw,
7
- LoadStrategy,
8
- LockMode,
9
- PopulateHint,
10
- QueryFlag,
11
- QueryHelper,
12
- raw,
13
- RawQueryFragment,
14
- Reference,
15
- ReferenceKind,
16
- serialize,
17
- Utils,
18
- ValidationError,
19
- } from '@mikro-orm/core';
2
+ import { EntityMetadata, helper, inspect, isRaw, LoadStrategy, LockMode, PopulateHint, QueryFlag, QueryHelper, raw, RawQueryFragment, Reference, ReferenceKind, serialize, Utils, ValidationError, } from '@mikro-orm/core';
20
3
  import { JoinType, QueryType } from './enums.js';
21
4
  import { QueryBuilderHelper } from './QueryBuilderHelper.js';
22
5
  import { CriteriaNodeFactory } from './CriteriaNodeFactory.js';
@@ -43,2270 +26,2162 @@ const FIELD_ALIAS_RE = /^(.+?)\s+as\s+(\w+)$/i;
43
26
  * ```
44
27
  */
45
28
  export class QueryBuilder {
46
- metadata;
47
- driver;
48
- context;
49
- connectionType;
50
- em;
51
- loggerContext;
52
- #state = _a.createDefaultState();
53
- #helper;
54
- #query;
55
- /** @internal */
56
- static createDefaultState() {
57
- return {
58
- aliasCounter: 0,
59
- explicitAlias: false,
60
- populateHintFinalized: false,
61
- joins: {},
62
- cond: {},
63
- orderBy: [],
64
- groupBy: [],
65
- having: {},
66
- comments: [],
67
- hintComments: [],
68
- subQueries: {},
69
- aliases: {},
70
- tptAlias: {},
71
- ctes: [],
72
- tptJoinsApplied: false,
73
- autoJoinedPaths: [],
74
- populate: [],
75
- populateMap: {},
76
- flags: new Set([QueryFlag.CONVERT_CUSTOM_TYPES]),
77
- finalized: false,
78
- joinedProps: new Map(),
79
- };
80
- }
81
- get mainAlias() {
82
- this.ensureFromClause();
83
- return this.#state.mainAlias;
84
- }
85
- get alias() {
86
- return this.mainAlias.aliasName;
87
- }
88
- get helper() {
89
- this.ensureFromClause();
90
- return this.#helper;
91
- }
92
- get type() {
93
- return this.#state.type ?? QueryType.SELECT;
94
- }
95
- /** @internal */
96
- get state() {
97
- return this.#state;
98
- }
99
- platform;
100
- /**
101
- * @internal
102
- */
103
- constructor(entityName, metadata, driver, context, alias, connectionType, em, loggerContext) {
104
- this.metadata = metadata;
105
- this.driver = driver;
106
- this.context = context;
107
- this.connectionType = connectionType;
108
- this.em = em;
109
- this.loggerContext = loggerContext;
110
- this.platform = this.driver.getPlatform();
111
- if (alias) {
112
- this.#state.aliasCounter++;
113
- this.#state.explicitAlias = true;
114
- }
115
- // @ts-expect-error union type does not match the overloaded method signature
116
- this.from(entityName, alias);
117
- }
118
- select(fields, distinct = false) {
119
- this.ensureNotFinalized();
120
- this.#state.fields = Utils.asArray(fields).flatMap(f => {
121
- if (typeof f !== 'string') {
122
- // Normalize sql.ref('prop') and sql.ref('prop').as('alias') to string form
123
- if (isRaw(f) && f.sql === '??' && f.params.length === 1) {
124
- return this.resolveNestedPath(String(f.params[0]));
125
- }
126
- if (isRaw(f) && f.sql === '?? as ??' && f.params.length === 2) {
127
- return `${this.resolveNestedPath(String(f.params[0]))} as ${String(f.params[1])}`;
128
- }
129
- return f;
130
- }
131
- const asMatch = FIELD_ALIAS_RE.exec(f);
132
- if (asMatch) {
133
- return `${this.resolveNestedPath(asMatch[1].trim())} as ${asMatch[2]}`;
134
- }
135
- return this.resolveNestedPath(f);
136
- });
137
- if (distinct) {
138
- this.#state.flags.add(QueryFlag.DISTINCT);
139
- }
140
- return this.init(QueryType.SELECT);
141
- }
142
- /**
143
- * Adds fields to an existing SELECT query.
144
- */
145
- addSelect(fields) {
146
- this.ensureNotFinalized();
147
- if (this.#state.type && this.#state.type !== QueryType.SELECT) {
148
- return this;
149
- }
150
- return this.select([...Utils.asArray(this.#state.fields), ...Utils.asArray(fields)]);
151
- }
152
- distinct() {
153
- this.ensureNotFinalized();
154
- return this.setFlag(QueryFlag.DISTINCT);
155
- }
156
- distinctOn(fields) {
157
- this.ensureNotFinalized();
158
- this.#state.distinctOn = Utils.asArray(fields);
159
- return this;
160
- }
161
- /**
162
- * Creates an INSERT query with the given data.
163
- *
164
- * @example
165
- * ```ts
166
- * await em.createQueryBuilder(User)
167
- * .insert({ name: 'John', email: 'john@example.com' })
168
- * .execute();
169
- *
170
- * // Bulk insert
171
- * await em.createQueryBuilder(User)
172
- * .insert([{ name: 'John' }, { name: 'Jane' }])
173
- * .execute();
174
- * ```
175
- */
176
- insert(data) {
177
- return this.init(QueryType.INSERT, data);
178
- }
179
- /**
180
- * Creates an UPDATE query with the given data.
181
- * Use `where()` to specify which rows to update.
182
- *
183
- * @example
184
- * ```ts
185
- * await em.createQueryBuilder(User)
186
- * .update({ name: 'John Doe' })
187
- * .where({ id: 1 })
188
- * .execute();
189
- * ```
190
- */
191
- update(data) {
192
- return this.init(QueryType.UPDATE, data);
193
- }
194
- /**
195
- * Creates a DELETE query.
196
- * Use `where()` to specify which rows to delete.
197
- *
198
- * @example
199
- * ```ts
200
- * await em.createQueryBuilder(User)
201
- * .delete()
202
- * .where({ id: 1 })
203
- * .execute();
204
- *
205
- * // Or pass the condition directly
206
- * await em.createQueryBuilder(User)
207
- * .delete({ isActive: false })
208
- * .execute();
209
- * ```
210
- */
211
- delete(cond) {
212
- return this.init(QueryType.DELETE, undefined, cond);
213
- }
214
- /**
215
- * Creates a TRUNCATE query to remove all rows from the table.
216
- */
217
- truncate() {
218
- return this.init(QueryType.TRUNCATE);
219
- }
220
- /**
221
- * Creates a COUNT query to count matching rows.
222
- *
223
- * @example
224
- * ```ts
225
- * const count = await em.createQueryBuilder(User)
226
- * .count()
227
- * .where({ isActive: true })
228
- * .execute('get');
229
- * ```
230
- */
231
- count(field, distinct = false) {
232
- if (field) {
233
- this.#state.fields = Utils.asArray(field);
234
- } else if (distinct || this.hasToManyJoins()) {
235
- this.#state.fields = this.mainAlias.meta.primaryKeys;
236
- } else {
237
- this.#state.fields = [raw('*')];
238
- }
239
- if (distinct) {
240
- this.#state.flags.add(QueryFlag.DISTINCT);
241
- }
242
- return this.init(QueryType.COUNT);
243
- }
244
- join(field, alias, cond = {}, type = JoinType.innerJoin, path, schema) {
245
- this.joinReference(field, alias, cond, type, path, schema);
246
- return this;
247
- }
248
- innerJoin(field, alias, cond = {}, schema) {
249
- this.join(field, alias, cond, JoinType.innerJoin, undefined, schema);
250
- return this;
251
- }
252
- innerJoinLateral(field, alias, cond = {}, schema) {
253
- return this.join(field, alias, cond, JoinType.innerJoinLateral, undefined, schema);
254
- }
255
- leftJoin(field, alias, cond = {}, schema) {
256
- return this.join(field, alias, cond, JoinType.leftJoin, undefined, schema);
257
- }
258
- leftJoinLateral(field, alias, cond = {}, schema) {
259
- return this.join(field, alias, cond, JoinType.leftJoinLateral, undefined, schema);
260
- }
261
- /**
262
- * Adds a JOIN clause and automatically selects the joined entity's fields.
263
- * This is useful for eager loading related entities.
264
- *
265
- * @example
266
- * ```ts
267
- * const qb = em.createQueryBuilder(Book, 'b');
268
- * qb.select('*')
269
- * .joinAndSelect('b.author', 'a')
270
- * .where({ 'a.name': 'John' });
271
- * ```
272
- */
273
- joinAndSelect(field, alias, cond = {}, type = JoinType.innerJoin, path, fields, schema) {
274
- if (!this.#state.type) {
275
- this.select('*');
276
- }
277
- let subquery;
278
- if (Array.isArray(field)) {
279
- const rawFragment = field[1] instanceof _a ? field[1].toRaw() : field[1];
280
- subquery = this.platform.formatQuery(rawFragment.sql, rawFragment.params);
281
- field = field[0];
282
- }
283
- const { prop, key } = this.joinReference(field, alias, cond, type, path, schema, subquery);
284
- const [fromAlias] = this.helper.splitField(field);
285
- if (subquery) {
286
- this.#state.joins[key].subquery = subquery;
287
- }
288
- const populate = this.#state.joinedProps.get(fromAlias);
289
- const item = { field: prop.name, strategy: LoadStrategy.JOINED, children: [] };
290
- if (populate) {
291
- populate.children.push(item);
292
- } else {
293
- // root entity
294
- this.#state.populate.push(item);
295
- }
296
- this.#state.joinedProps.set(alias, item);
297
- this.addSelect(this.getFieldsForJoinedLoad(prop, alias, fields));
298
- return this;
299
- }
300
- leftJoinAndSelect(field, alias, cond = {}, fields, schema) {
301
- return this.joinAndSelect(field, alias, cond, JoinType.leftJoin, undefined, fields, schema);
302
- }
303
- leftJoinLateralAndSelect(field, alias, cond = {}, fields, schema) {
304
- this.joinAndSelect(field, alias, cond, JoinType.leftJoinLateral, undefined, fields, schema);
305
- return this;
306
- }
307
- innerJoinAndSelect(field, alias, cond = {}, fields, schema) {
308
- return this.joinAndSelect(field, alias, cond, JoinType.innerJoin, undefined, fields, schema);
309
- }
310
- innerJoinLateralAndSelect(field, alias, cond = {}, fields, schema) {
311
- this.joinAndSelect(field, alias, cond, JoinType.innerJoinLateral, undefined, fields, schema);
312
- return this;
313
- }
314
- getFieldsForJoinedLoad(prop, alias, explicitFields) {
315
- const fields = [];
316
- const populate = [];
317
- const joinKey = Object.keys(this.#state.joins).find(join => join.endsWith(`#${alias}`));
318
- const targetMeta = prop.targetMeta;
319
- const schema = this.#state.schema ?? (targetMeta.schema !== '*' ? targetMeta.schema : undefined);
320
- if (joinKey) {
321
- const path = this.#state.joins[joinKey].path.split('.').slice(1);
322
- let children = this.#state.populate;
323
- for (let i = 0; i < path.length; i++) {
324
- const child = children.filter(hint => {
325
- const [propName] = hint.field.split(':', 2);
326
- return propName === path[i];
29
+ metadata;
30
+ driver;
31
+ context;
32
+ connectionType;
33
+ em;
34
+ loggerContext;
35
+ #state = _a.createDefaultState();
36
+ #helper;
37
+ #query;
38
+ /** @internal */
39
+ static createDefaultState() {
40
+ return {
41
+ aliasCounter: 0,
42
+ explicitAlias: false,
43
+ populateHintFinalized: false,
44
+ joins: {},
45
+ cond: {},
46
+ orderBy: [],
47
+ groupBy: [],
48
+ having: {},
49
+ comments: [],
50
+ hintComments: [],
51
+ subQueries: {},
52
+ aliases: {},
53
+ tptAlias: {},
54
+ ctes: [],
55
+ tptJoinsApplied: false,
56
+ autoJoinedPaths: [],
57
+ populate: [],
58
+ populateMap: {},
59
+ flags: new Set([QueryFlag.CONVERT_CUSTOM_TYPES]),
60
+ finalized: false,
61
+ joinedProps: new Map(),
62
+ };
63
+ }
64
+ get mainAlias() {
65
+ this.ensureFromClause();
66
+ return this.#state.mainAlias;
67
+ }
68
+ get alias() {
69
+ return this.mainAlias.aliasName;
70
+ }
71
+ get helper() {
72
+ this.ensureFromClause();
73
+ return this.#helper;
74
+ }
75
+ get type() {
76
+ return this.#state.type ?? QueryType.SELECT;
77
+ }
78
+ /** @internal */
79
+ get state() {
80
+ return this.#state;
81
+ }
82
+ platform;
83
+ /**
84
+ * @internal
85
+ */
86
+ constructor(entityName, metadata, driver, context, alias, connectionType, em, loggerContext) {
87
+ this.metadata = metadata;
88
+ this.driver = driver;
89
+ this.context = context;
90
+ this.connectionType = connectionType;
91
+ this.em = em;
92
+ this.loggerContext = loggerContext;
93
+ this.platform = this.driver.getPlatform();
94
+ if (alias) {
95
+ this.#state.aliasCounter++;
96
+ this.#state.explicitAlias = true;
97
+ }
98
+ // @ts-expect-error union type does not match the overloaded method signature
99
+ this.from(entityName, alias);
100
+ }
101
+ select(fields, distinct = false) {
102
+ this.ensureNotFinalized();
103
+ this.#state.fields = Utils.asArray(fields).flatMap(f => {
104
+ if (typeof f !== 'string') {
105
+ // Normalize sql.ref('prop') and sql.ref('prop').as('alias') to string form
106
+ if (isRaw(f) && f.sql === '??' && f.params.length === 1) {
107
+ return this.resolveNestedPath(String(f.params[0]));
108
+ }
109
+ if (isRaw(f) && f.sql === '?? as ??' && f.params.length === 2) {
110
+ return `${this.resolveNestedPath(String(f.params[0]))} as ${String(f.params[1])}`;
111
+ }
112
+ return f;
113
+ }
114
+ const asMatch = FIELD_ALIAS_RE.exec(f);
115
+ if (asMatch) {
116
+ return `${this.resolveNestedPath(asMatch[1].trim())} as ${asMatch[2]}`;
117
+ }
118
+ return this.resolveNestedPath(f);
327
119
  });
328
- children = child.flatMap(c => c.children);
329
- }
330
- populate.push(...children);
331
- }
332
- for (const p of targetMeta.getPrimaryProps()) {
333
- fields.push(...this.driver.mapPropToFieldNames(this, p, alias, targetMeta, schema));
334
- }
335
- if (explicitFields && explicitFields.length > 0) {
336
- for (const field of explicitFields) {
337
- const [a, f] = this.helper.splitField(field);
338
- const p = targetMeta.properties[f];
339
- if (p) {
340
- fields.push(...this.driver.mapPropToFieldNames(this, p, alias, targetMeta, schema));
341
- } else {
342
- fields.push(`${a}.${f} as ${a}__${f}`);
343
- }
344
- }
345
- }
346
- targetMeta.props
347
- .filter(prop => {
348
- if (!explicitFields || explicitFields.length === 0) {
349
- return this.platform.shouldHaveColumn(prop, populate);
350
- }
351
- return prop.primary && !explicitFields.includes(prop.name) && !explicitFields.includes(`${alias}.${prop.name}`);
352
- })
353
- .forEach(prop => fields.push(...this.driver.mapPropToFieldNames(this, prop, alias, targetMeta, schema)));
354
- return fields;
355
- }
356
- /**
357
- * Apply filters to the QB where condition.
358
- */
359
- async applyFilters(filterOptions = {}) {
360
- /* v8 ignore next */
361
- if (!this.em) {
362
- throw new Error('Cannot apply filters, this QueryBuilder is not attached to an EntityManager');
363
- }
364
- const cond = await this.em.applyFilters(this.mainAlias.entityName, {}, filterOptions, 'read');
365
- this.andWhere(cond);
366
- }
367
- /**
368
- * @internal
369
- */
370
- scheduleFilterCheck(path) {
371
- this.#state.autoJoinedPaths.push(path);
372
- }
373
- /**
374
- * @internal
375
- */
376
- async applyJoinedFilters(em, filterOptions) {
377
- for (const path of this.#state.autoJoinedPaths) {
378
- const join = this.getJoinForPath(path);
379
- if (join.type === JoinType.pivotJoin) {
380
- continue;
381
- }
382
- filterOptions = QueryHelper.mergePropertyFilters(join.prop.filters, filterOptions);
383
- let cond = await em.applyFilters(join.prop.targetMeta.class, join.cond, filterOptions, 'read');
384
- const criteriaNode = CriteriaNodeFactory.createNode(this.metadata, join.prop.targetMeta.class, cond);
385
- cond = criteriaNode.process(this, {
386
- matchPopulateJoins: true,
387
- filter: true,
388
- alias: join.alias,
389
- ignoreBranching: true,
390
- parentPath: join.path,
391
- });
392
- if (Utils.hasObjectKeys(cond) || RawQueryFragment.hasObjectFragments(cond)) {
393
- // remove nested filters, we only care about scalars here, nesting would require another join branch
394
- for (const key of Object.keys(cond)) {
395
- if (
396
- Utils.isPlainObject(cond[key]) &&
397
- Object.keys(cond[key]).every(
398
- k => !(Utils.isOperator(k) && !['$some', '$none', '$every', '$size'].includes(k)),
399
- )
400
- ) {
401
- delete cond[key];
402
- }
403
- }
404
- if (Utils.hasObjectKeys(join.cond) || RawQueryFragment.hasObjectFragments(join.cond)) {
405
- /* v8 ignore next */
406
- join.cond = { $and: [join.cond, cond] };
407
- } else {
408
- join.cond = { ...cond };
409
- }
410
- }
411
- }
412
- }
413
- withSubQuery(subQuery, alias) {
414
- this.ensureNotFinalized();
415
- if (isRaw(subQuery)) {
416
- this.#state.subQueries[alias] = this.platform.formatQuery(subQuery.sql, subQuery.params);
417
- } else {
418
- this.#state.subQueries[alias] = subQuery.toString();
419
- }
420
- return this;
421
- }
422
- where(cond, params, operator) {
423
- this.ensureNotFinalized();
424
- let processedCond;
425
- if (isRaw(cond)) {
426
- const sql = this.platform.formatQuery(cond.sql, cond.params);
427
- processedCond = { [raw(`(${sql})`)]: Utils.asArray(params) };
428
- operator ??= '$and';
429
- } else if (typeof cond === 'string') {
430
- processedCond = { [raw(`(${cond})`, Utils.asArray(params))]: [] };
431
- operator ??= '$and';
432
- } else {
433
- processedCond = QueryHelper.processWhere({
434
- where: cond,
435
- entityName: this.mainAlias.entityName,
436
- metadata: this.metadata,
437
- platform: this.platform,
438
- aliasMap: this.getAliasMap(),
439
- aliased: [QueryType.SELECT, QueryType.COUNT].includes(this.type),
440
- convertCustomTypes: this.#state.flags.has(QueryFlag.CONVERT_CUSTOM_TYPES),
441
- });
442
- }
443
- const op = operator || params;
444
- const topLevel =
445
- !op || !(Utils.hasObjectKeys(this.#state.cond) || RawQueryFragment.hasObjectFragments(this.#state.cond));
446
- const criteriaNode = CriteriaNodeFactory.createNode(this.metadata, this.mainAlias.entityName, processedCond);
447
- const ignoreBranching = this.#state.resolvedPopulateWhere === 'infer';
448
- if (
449
- [QueryType.UPDATE, QueryType.DELETE].includes(this.type) &&
450
- criteriaNode.willAutoJoin(this, undefined, { ignoreBranching })
451
- ) {
452
- // use sub-query to support joining
453
- this.setFlag(this.type === QueryType.UPDATE ? QueryFlag.UPDATE_SUB_QUERY : QueryFlag.DELETE_SUB_QUERY);
454
- this.select(this.mainAlias.meta.primaryKeys, true);
455
- }
456
- if (topLevel) {
457
- this.#state.cond = criteriaNode.process(this, { ignoreBranching });
458
- } else if (Array.isArray(this.#state.cond[op])) {
459
- this.#state.cond[op].push(criteriaNode.process(this, { ignoreBranching }));
460
- } else {
461
- const cond1 = [this.#state.cond, criteriaNode.process(this, { ignoreBranching })];
462
- this.#state.cond = { [op]: cond1 };
463
- }
464
- if (this.#state.onConflict) {
465
- this.#state.onConflict[this.#state.onConflict.length - 1].where = this.helper.processOnConflictCondition(
466
- this.#state.cond,
467
- this.#state.schema,
468
- );
469
- this.#state.cond = {};
470
- }
471
- return this;
472
- }
473
- andWhere(cond, params) {
474
- return this.where(cond, params, '$and');
475
- }
476
- orWhere(cond, params) {
477
- return this.where(cond, params, '$or');
478
- }
479
- orderBy(orderBy) {
480
- return this.processOrderBy(orderBy, true);
481
- }
482
- andOrderBy(orderBy) {
483
- return this.processOrderBy(orderBy, false);
484
- }
485
- processOrderBy(orderBy, reset = true) {
486
- this.ensureNotFinalized();
487
- if (reset) {
488
- this.#state.orderBy = [];
489
- }
490
- const selectAliases = this.getSelectAliases();
491
- Utils.asArray(orderBy).forEach(orig => {
492
- // Shallow clone to avoid mutating the caller's object — safe because the clone
493
- // is only used within this loop iteration and `orig` is not referenced afterward.
494
- const o = { ...orig };
495
- // Wrap known select aliases in raw() so they bypass property validation and alias prefixing
496
- for (const key of Object.keys(o)) {
497
- if (selectAliases.has(key)) {
498
- o[raw('??', [key])] = o[key];
499
- delete o[key];
500
- }
501
- }
502
- this.helper.validateQueryOrder(o);
503
- const processed = QueryHelper.processWhere({
504
- where: o,
505
- entityName: this.mainAlias.entityName,
506
- metadata: this.metadata,
507
- platform: this.platform,
508
- aliasMap: this.getAliasMap(),
509
- aliased: [QueryType.SELECT, QueryType.COUNT].includes(this.type),
510
- convertCustomTypes: false,
511
- type: 'orderBy',
512
- });
513
- this.#state.orderBy.push(
514
- CriteriaNodeFactory.createNode(this.metadata, this.mainAlias.entityName, processed).process(this, {
515
- matchPopulateJoins: true,
516
- type: 'orderBy',
517
- }),
518
- );
519
- });
520
- return this;
521
- }
522
- /** Collect custom aliases from select fields (stored as 'resolved as alias' strings by select()). */
523
- getSelectAliases() {
524
- const aliases = new Set();
525
- for (const field of this.#state.fields ?? []) {
526
- if (typeof field === 'string') {
527
- const m = FIELD_ALIAS_RE.exec(field);
528
- if (m) {
529
- aliases.add(m[2]);
530
- }
531
- }
532
- }
533
- return aliases;
534
- }
535
- groupBy(fields) {
536
- this.ensureNotFinalized();
537
- this.#state.groupBy = Utils.asArray(fields).flatMap(f => {
538
- if (typeof f !== 'string') {
539
- // Normalize sql.ref('prop') to string for proper formula resolution
540
- if (isRaw(f) && f.sql === '??' && f.params.length === 1) {
541
- return this.resolveNestedPath(String(f.params[0]));
542
- }
543
- return f;
544
- }
545
- return this.resolveNestedPath(f);
546
- });
547
- return this;
548
- }
549
- /**
550
- * Adds a HAVING clause to the query, typically used with GROUP BY.
551
- *
552
- * @example
553
- * ```ts
554
- * qb.select([raw('count(*) as count'), 'status'])
555
- * .groupBy('status')
556
- * .having({ count: { $gt: 5 } });
557
- * ```
558
- */
559
- having(cond = {}, params, operator) {
560
- this.ensureNotFinalized();
561
- if (typeof cond === 'string') {
562
- cond = { [raw(`(${cond})`, params)]: [] };
563
- }
564
- const processed = CriteriaNodeFactory.createNode(
565
- this.metadata,
566
- this.mainAlias.entityName,
567
- cond,
568
- undefined,
569
- undefined,
570
- false,
571
- ).process(this, { type: 'having' });
572
- if (!this.#state.having || !operator) {
573
- this.#state.having = processed;
574
- } else {
575
- const cond1 = [this.#state.having, processed];
576
- this.#state.having = { [operator]: cond1 };
577
- }
578
- return this;
579
- }
580
- andHaving(cond, params) {
581
- return this.having(cond, params, '$and');
582
- }
583
- orHaving(cond, params) {
584
- return this.having(cond, params, '$or');
585
- }
586
- onConflict(fields = []) {
587
- const meta = this.mainAlias.meta;
588
- this.ensureNotFinalized();
589
- this.#state.onConflict ??= [];
590
- this.#state.onConflict.push({
591
- fields: isRaw(fields)
592
- ? fields
593
- : Utils.asArray(fields).flatMap(f => {
594
- const key = f.toString();
595
- /* v8 ignore next */
596
- return meta.properties[key]?.fieldNames ?? [key];
597
- }),
598
- });
599
- return this;
600
- }
601
- ignore() {
602
- if (!this.#state.onConflict) {
603
- throw new Error('You need to call `qb.onConflict()` first to use `qb.ignore()`');
604
- }
605
- this.#state.onConflict[this.#state.onConflict.length - 1].ignore = true;
606
- return this;
607
- }
608
- merge(data) {
609
- if (!this.#state.onConflict) {
610
- throw new Error('You need to call `qb.onConflict()` first to use `qb.merge()`');
611
- }
612
- if (Array.isArray(data) && data.length === 0) {
613
- return this.ignore();
614
- }
615
- this.#state.onConflict[this.#state.onConflict.length - 1].merge = data;
616
- return this;
617
- }
618
- returning(fields) {
619
- this.#state.returning = Utils.asArray(fields);
620
- return this;
621
- }
622
- /**
623
- * @internal
624
- */
625
- populate(populate, populateWhere, populateFilter) {
626
- this.ensureNotFinalized();
627
- this.#state.populate = populate;
628
- this.#state.populateWhere = populateWhere;
629
- this.#state.populateFilter = populateFilter;
630
- return this;
631
- }
632
- /**
633
- * Sets a LIMIT clause to restrict the number of results.
634
- *
635
- * @example
636
- * ```ts
637
- * qb.select('*').limit(10); // First 10 results
638
- * qb.select('*').limit(10, 20); // 10 results starting from offset 20
639
- * ```
640
- */
641
- limit(limit, offset = 0) {
642
- this.ensureNotFinalized();
643
- this.#state.limit = limit;
644
- if (offset) {
645
- this.offset(offset);
646
- }
647
- return this;
648
- }
649
- /**
650
- * Sets an OFFSET clause to skip a number of results.
651
- *
652
- * @example
653
- * ```ts
654
- * qb.select('*').limit(10).offset(20); // Results 21-30
655
- * ```
656
- */
657
- offset(offset) {
658
- this.ensureNotFinalized();
659
- this.#state.offset = offset;
660
- return this;
661
- }
662
- withSchema(schema) {
663
- this.ensureNotFinalized();
664
- this.#state.schema = schema;
665
- return this;
666
- }
667
- setLockMode(mode, tables) {
668
- this.ensureNotFinalized();
669
- if (mode != null && ![LockMode.OPTIMISTIC, LockMode.NONE].includes(mode) && !this.context) {
670
- throw ValidationError.transactionRequired();
671
- }
672
- this.#state.lockMode = mode;
673
- this.#state.lockTables = tables;
674
- return this;
675
- }
676
- setFlushMode(flushMode) {
677
- this.ensureNotFinalized();
678
- this.#state.flushMode = flushMode;
679
- return this;
680
- }
681
- setFlag(flag) {
682
- this.ensureNotFinalized();
683
- this.#state.flags.add(flag);
684
- return this;
685
- }
686
- unsetFlag(flag) {
687
- this.ensureNotFinalized();
688
- this.#state.flags.delete(flag);
689
- return this;
690
- }
691
- hasFlag(flag) {
692
- return this.#state.flags.has(flag);
693
- }
694
- cache(config = true) {
695
- this.ensureNotFinalized();
696
- this.#state.cache = config;
697
- return this;
698
- }
699
- /**
700
- * Adds index hint to the FROM clause.
701
- */
702
- indexHint(sql) {
703
- this.ensureNotFinalized();
704
- this.#state.indexHint = sql;
705
- return this;
706
- }
707
- /**
708
- * Adds COLLATE clause to ORDER BY expressions.
709
- */
710
- collation(collation) {
711
- this.ensureNotFinalized();
712
- this.#state.collation = collation;
713
- return this;
714
- }
715
- /**
716
- * Prepend comment to the sql query using the syntax `/* ... *&#8205;/`. Some characters are forbidden such as `/*, *&#8205;/` and `?`.
717
- */
718
- comment(comment) {
719
- this.ensureNotFinalized();
720
- this.#state.comments.push(...Utils.asArray(comment));
721
- return this;
722
- }
723
- /**
724
- * Add hints to the query using comment-like syntax `/*+ ... *&#8205;/`. MySQL and Oracle use this syntax for optimizer hints.
725
- * Also various DB proxies and routers use this syntax to pass hints to alter their behavior. In other dialects the hints
726
- * are ignored as simple comments.
727
- */
728
- hintComment(comment) {
729
- this.ensureNotFinalized();
730
- this.#state.hintComments.push(...Utils.asArray(comment));
731
- return this;
732
- }
733
- from(target, aliasName) {
734
- this.ensureNotFinalized();
735
- if (target instanceof _a) {
736
- this.fromSubQuery(target, aliasName);
737
- } else if (typeof target === 'string' && !this.metadata.find(target)) {
738
- this.fromRawTable(target, aliasName);
739
- } else {
740
- if (aliasName && this.#state.mainAlias && Utils.className(target) !== this.#state.mainAlias.aliasName) {
741
- throw new Error(
742
- `Cannot override the alias to '${aliasName}' since a query already contains references to '${this.#state.mainAlias.aliasName}'`,
743
- );
744
- }
745
- this.fromEntityName(target, aliasName);
746
- }
747
- return this;
748
- }
749
- getNativeQuery(processVirtualEntity = true) {
750
- if (this.#state.unionQuery) {
751
- if (!this.#query?.qb) {
120
+ if (distinct) {
121
+ this.#state.flags.add(QueryFlag.DISTINCT);
122
+ }
123
+ return this.init(QueryType.SELECT);
124
+ }
125
+ /**
126
+ * Adds fields to an existing SELECT query.
127
+ */
128
+ addSelect(fields) {
129
+ this.ensureNotFinalized();
130
+ if (this.#state.type && this.#state.type !== QueryType.SELECT) {
131
+ return this;
132
+ }
133
+ return this.select([...Utils.asArray(this.#state.fields), ...Utils.asArray(fields)]);
134
+ }
135
+ distinct() {
136
+ this.ensureNotFinalized();
137
+ return this.setFlag(QueryFlag.DISTINCT);
138
+ }
139
+ distinctOn(fields) {
140
+ this.ensureNotFinalized();
141
+ this.#state.distinctOn = Utils.asArray(fields);
142
+ return this;
143
+ }
144
+ /**
145
+ * Creates an INSERT query with the given data.
146
+ *
147
+ * @example
148
+ * ```ts
149
+ * await em.createQueryBuilder(User)
150
+ * .insert({ name: 'John', email: 'john@example.com' })
151
+ * .execute();
152
+ *
153
+ * // Bulk insert
154
+ * await em.createQueryBuilder(User)
155
+ * .insert([{ name: 'John' }, { name: 'Jane' }])
156
+ * .execute();
157
+ * ```
158
+ */
159
+ insert(data) {
160
+ return this.init(QueryType.INSERT, data);
161
+ }
162
+ /**
163
+ * Creates an UPDATE query with the given data.
164
+ * Use `where()` to specify which rows to update.
165
+ *
166
+ * @example
167
+ * ```ts
168
+ * await em.createQueryBuilder(User)
169
+ * .update({ name: 'John Doe' })
170
+ * .where({ id: 1 })
171
+ * .execute();
172
+ * ```
173
+ */
174
+ update(data) {
175
+ return this.init(QueryType.UPDATE, data);
176
+ }
177
+ /**
178
+ * Creates a DELETE query.
179
+ * Use `where()` to specify which rows to delete.
180
+ *
181
+ * @example
182
+ * ```ts
183
+ * await em.createQueryBuilder(User)
184
+ * .delete()
185
+ * .where({ id: 1 })
186
+ * .execute();
187
+ *
188
+ * // Or pass the condition directly
189
+ * await em.createQueryBuilder(User)
190
+ * .delete({ isActive: false })
191
+ * .execute();
192
+ * ```
193
+ */
194
+ delete(cond) {
195
+ return this.init(QueryType.DELETE, undefined, cond);
196
+ }
197
+ /**
198
+ * Creates a TRUNCATE query to remove all rows from the table.
199
+ */
200
+ truncate() {
201
+ return this.init(QueryType.TRUNCATE);
202
+ }
203
+ /**
204
+ * Creates a COUNT query to count matching rows.
205
+ *
206
+ * @example
207
+ * ```ts
208
+ * const count = await em.createQueryBuilder(User)
209
+ * .count()
210
+ * .where({ isActive: true })
211
+ * .execute('get');
212
+ * ```
213
+ */
214
+ count(field, distinct = false) {
215
+ if (field) {
216
+ this.#state.fields = Utils.asArray(field);
217
+ }
218
+ else if (distinct || this.hasToManyJoins()) {
219
+ this.#state.fields = this.mainAlias.meta.primaryKeys;
220
+ }
221
+ else {
222
+ this.#state.fields = [raw('*')];
223
+ }
224
+ if (distinct) {
225
+ this.#state.flags.add(QueryFlag.DISTINCT);
226
+ }
227
+ return this.init(QueryType.COUNT);
228
+ }
229
+ join(field, alias, cond = {}, type = JoinType.innerJoin, path, schema) {
230
+ this.joinReference(field, alias, cond, type, path, schema);
231
+ return this;
232
+ }
233
+ innerJoin(field, alias, cond = {}, schema) {
234
+ this.join(field, alias, cond, JoinType.innerJoin, undefined, schema);
235
+ return this;
236
+ }
237
+ innerJoinLateral(field, alias, cond = {}, schema) {
238
+ return this.join(field, alias, cond, JoinType.innerJoinLateral, undefined, schema);
239
+ }
240
+ leftJoin(field, alias, cond = {}, schema) {
241
+ return this.join(field, alias, cond, JoinType.leftJoin, undefined, schema);
242
+ }
243
+ leftJoinLateral(field, alias, cond = {}, schema) {
244
+ return this.join(field, alias, cond, JoinType.leftJoinLateral, undefined, schema);
245
+ }
246
+ /**
247
+ * Adds a JOIN clause and automatically selects the joined entity's fields.
248
+ * This is useful for eager loading related entities.
249
+ *
250
+ * @example
251
+ * ```ts
252
+ * const qb = em.createQueryBuilder(Book, 'b');
253
+ * qb.select('*')
254
+ * .joinAndSelect('b.author', 'a')
255
+ * .where({ 'a.name': 'John' });
256
+ * ```
257
+ */
258
+ joinAndSelect(field, alias, cond = {}, type = JoinType.innerJoin, path, fields, schema) {
259
+ if (!this.#state.type) {
260
+ this.select('*');
261
+ }
262
+ let subquery;
263
+ if (Array.isArray(field)) {
264
+ const rawFragment = field[1] instanceof _a ? field[1].toRaw() : field[1];
265
+ subquery = this.platform.formatQuery(rawFragment.sql, rawFragment.params);
266
+ field = field[0];
267
+ }
268
+ const { prop, key } = this.joinReference(field, alias, cond, type, path, schema, subquery);
269
+ const [fromAlias] = this.helper.splitField(field);
270
+ if (subquery) {
271
+ this.#state.joins[key].subquery = subquery;
272
+ }
273
+ const populate = this.#state.joinedProps.get(fromAlias);
274
+ const item = { field: prop.name, strategy: LoadStrategy.JOINED, children: [] };
275
+ if (populate) {
276
+ populate.children.push(item);
277
+ }
278
+ else {
279
+ // root entity
280
+ this.#state.populate.push(item);
281
+ }
282
+ this.#state.joinedProps.set(alias, item);
283
+ this.addSelect(this.getFieldsForJoinedLoad(prop, alias, fields));
284
+ return this;
285
+ }
286
+ leftJoinAndSelect(field, alias, cond = {}, fields, schema) {
287
+ return this.joinAndSelect(field, alias, cond, JoinType.leftJoin, undefined, fields, schema);
288
+ }
289
+ leftJoinLateralAndSelect(field, alias, cond = {}, fields, schema) {
290
+ this.joinAndSelect(field, alias, cond, JoinType.leftJoinLateral, undefined, fields, schema);
291
+ return this;
292
+ }
293
+ innerJoinAndSelect(field, alias, cond = {}, fields, schema) {
294
+ return this.joinAndSelect(field, alias, cond, JoinType.innerJoin, undefined, fields, schema);
295
+ }
296
+ innerJoinLateralAndSelect(field, alias, cond = {}, fields, schema) {
297
+ this.joinAndSelect(field, alias, cond, JoinType.innerJoinLateral, undefined, fields, schema);
298
+ return this;
299
+ }
300
+ getFieldsForJoinedLoad(prop, alias, explicitFields) {
301
+ const fields = [];
302
+ const populate = [];
303
+ const joinKey = Object.keys(this.#state.joins).find(join => join.endsWith(`#${alias}`));
304
+ const targetMeta = prop.targetMeta;
305
+ const schema = this.#state.schema ?? (targetMeta.schema !== '*' ? targetMeta.schema : undefined);
306
+ if (joinKey) {
307
+ const path = this.#state.joins[joinKey].path.split('.').slice(1);
308
+ let children = this.#state.populate;
309
+ for (let i = 0; i < path.length; i++) {
310
+ const child = children.filter(hint => {
311
+ const [propName] = hint.field.split(':', 2);
312
+ return propName === path[i];
313
+ });
314
+ children = child.flatMap(c => c.children);
315
+ }
316
+ populate.push(...children);
317
+ }
318
+ for (const p of targetMeta.getPrimaryProps()) {
319
+ fields.push(...this.driver.mapPropToFieldNames(this, p, alias, targetMeta, schema));
320
+ }
321
+ if (explicitFields && explicitFields.length > 0) {
322
+ for (const field of explicitFields) {
323
+ const [a, f] = this.helper.splitField(field);
324
+ const p = targetMeta.properties[f];
325
+ if (p) {
326
+ fields.push(...this.driver.mapPropToFieldNames(this, p, alias, targetMeta, schema));
327
+ }
328
+ else {
329
+ fields.push(`${a}.${f} as ${a}__${f}`);
330
+ }
331
+ }
332
+ }
333
+ targetMeta.props
334
+ .filter(prop => {
335
+ if (!explicitFields || explicitFields.length === 0) {
336
+ return this.platform.shouldHaveColumn(prop, populate);
337
+ }
338
+ return prop.primary && !explicitFields.includes(prop.name) && !explicitFields.includes(`${alias}.${prop.name}`);
339
+ })
340
+ .forEach(prop => fields.push(...this.driver.mapPropToFieldNames(this, prop, alias, targetMeta, schema)));
341
+ return fields;
342
+ }
343
+ /**
344
+ * Apply filters to the QB where condition.
345
+ */
346
+ async applyFilters(filterOptions = {}) {
347
+ /* v8 ignore next */
348
+ if (!this.em) {
349
+ throw new Error('Cannot apply filters, this QueryBuilder is not attached to an EntityManager');
350
+ }
351
+ const cond = await this.em.applyFilters(this.mainAlias.entityName, {}, filterOptions, 'read');
352
+ this.andWhere(cond);
353
+ }
354
+ /**
355
+ * @internal
356
+ */
357
+ scheduleFilterCheck(path) {
358
+ this.#state.autoJoinedPaths.push(path);
359
+ }
360
+ /**
361
+ * @internal
362
+ */
363
+ async applyJoinedFilters(em, filterOptions) {
364
+ for (const path of this.#state.autoJoinedPaths) {
365
+ const join = this.getJoinForPath(path);
366
+ if (join.type === JoinType.pivotJoin) {
367
+ continue;
368
+ }
369
+ filterOptions = QueryHelper.mergePropertyFilters(join.prop.filters, filterOptions);
370
+ let cond = await em.applyFilters(join.prop.targetMeta.class, join.cond, filterOptions, 'read');
371
+ const criteriaNode = CriteriaNodeFactory.createNode(this.metadata, join.prop.targetMeta.class, cond);
372
+ cond = criteriaNode.process(this, {
373
+ matchPopulateJoins: true,
374
+ filter: true,
375
+ alias: join.alias,
376
+ ignoreBranching: true,
377
+ parentPath: join.path,
378
+ });
379
+ if (Utils.hasObjectKeys(cond) || RawQueryFragment.hasObjectFragments(cond)) {
380
+ // remove nested filters, we only care about scalars here, nesting would require another join branch
381
+ for (const key of Object.keys(cond)) {
382
+ if (Utils.isPlainObject(cond[key]) &&
383
+ Object.keys(cond[key]).every(k => !(Utils.isOperator(k) && !['$some', '$none', '$every', '$size'].includes(k)))) {
384
+ delete cond[key];
385
+ }
386
+ }
387
+ if (Utils.hasObjectKeys(join.cond) || RawQueryFragment.hasObjectFragments(join.cond)) {
388
+ /* v8 ignore next */
389
+ join.cond = { $and: [join.cond, cond] };
390
+ }
391
+ else {
392
+ join.cond = { ...cond };
393
+ }
394
+ }
395
+ }
396
+ }
397
+ withSubQuery(subQuery, alias) {
398
+ this.ensureNotFinalized();
399
+ if (isRaw(subQuery)) {
400
+ this.#state.subQueries[alias] = this.platform.formatQuery(subQuery.sql, subQuery.params);
401
+ }
402
+ else {
403
+ this.#state.subQueries[alias] = subQuery.toString();
404
+ }
405
+ return this;
406
+ }
407
+ where(cond, params, operator) {
408
+ this.ensureNotFinalized();
409
+ let processedCond;
410
+ if (isRaw(cond)) {
411
+ const sql = this.platform.formatQuery(cond.sql, cond.params);
412
+ processedCond = { [raw(`(${sql})`)]: Utils.asArray(params) };
413
+ operator ??= '$and';
414
+ }
415
+ else if (typeof cond === 'string') {
416
+ processedCond = { [raw(`(${cond})`, Utils.asArray(params))]: [] };
417
+ operator ??= '$and';
418
+ }
419
+ else {
420
+ processedCond = QueryHelper.processWhere({
421
+ where: cond,
422
+ entityName: this.mainAlias.entityName,
423
+ metadata: this.metadata,
424
+ platform: this.platform,
425
+ aliasMap: this.getAliasMap(),
426
+ aliased: [QueryType.SELECT, QueryType.COUNT].includes(this.type),
427
+ convertCustomTypes: this.#state.flags.has(QueryFlag.CONVERT_CUSTOM_TYPES),
428
+ });
429
+ }
430
+ const op = operator || params;
431
+ const topLevel = !op || !(Utils.hasObjectKeys(this.#state.cond) || RawQueryFragment.hasObjectFragments(this.#state.cond));
432
+ const criteriaNode = CriteriaNodeFactory.createNode(this.metadata, this.mainAlias.entityName, processedCond);
433
+ const ignoreBranching = this.#state.resolvedPopulateWhere === 'infer';
434
+ if ([QueryType.UPDATE, QueryType.DELETE].includes(this.type) &&
435
+ criteriaNode.willAutoJoin(this, undefined, { ignoreBranching })) {
436
+ // use sub-query to support joining
437
+ this.setFlag(this.type === QueryType.UPDATE ? QueryFlag.UPDATE_SUB_QUERY : QueryFlag.DELETE_SUB_QUERY);
438
+ this.select(this.mainAlias.meta.primaryKeys, true);
439
+ }
440
+ if (topLevel) {
441
+ this.#state.cond = criteriaNode.process(this, { ignoreBranching });
442
+ }
443
+ else if (Array.isArray(this.#state.cond[op])) {
444
+ this.#state.cond[op].push(criteriaNode.process(this, { ignoreBranching }));
445
+ }
446
+ else {
447
+ const cond1 = [this.#state.cond, criteriaNode.process(this, { ignoreBranching })];
448
+ this.#state.cond = { [op]: cond1 };
449
+ }
450
+ if (this.#state.onConflict) {
451
+ this.#state.onConflict[this.#state.onConflict.length - 1].where = this.helper.processOnConflictCondition(this.#state.cond, this.#state.schema);
452
+ this.#state.cond = {};
453
+ }
454
+ return this;
455
+ }
456
+ andWhere(cond, params) {
457
+ return this.where(cond, params, '$and');
458
+ }
459
+ orWhere(cond, params) {
460
+ return this.where(cond, params, '$or');
461
+ }
462
+ orderBy(orderBy) {
463
+ return this.processOrderBy(orderBy, true);
464
+ }
465
+ andOrderBy(orderBy) {
466
+ return this.processOrderBy(orderBy, false);
467
+ }
468
+ processOrderBy(orderBy, reset = true) {
469
+ this.ensureNotFinalized();
470
+ if (reset) {
471
+ this.#state.orderBy = [];
472
+ }
473
+ const selectAliases = this.getSelectAliases();
474
+ Utils.asArray(orderBy).forEach(orig => {
475
+ // Shallow clone to avoid mutating the caller's object — safe because the clone
476
+ // is only used within this loop iteration and `orig` is not referenced afterward.
477
+ const o = { ...orig };
478
+ // Wrap known select aliases in raw() so they bypass property validation and alias prefixing
479
+ for (const key of Object.keys(o)) {
480
+ if (selectAliases.has(key)) {
481
+ o[raw('??', [key])] = o[key];
482
+ delete o[key];
483
+ }
484
+ }
485
+ this.helper.validateQueryOrder(o);
486
+ const processed = QueryHelper.processWhere({
487
+ where: o,
488
+ entityName: this.mainAlias.entityName,
489
+ metadata: this.metadata,
490
+ platform: this.platform,
491
+ aliasMap: this.getAliasMap(),
492
+ aliased: [QueryType.SELECT, QueryType.COUNT].includes(this.type),
493
+ convertCustomTypes: false,
494
+ type: 'orderBy',
495
+ });
496
+ this.#state.orderBy.push(CriteriaNodeFactory.createNode(this.metadata, this.mainAlias.entityName, processed).process(this, {
497
+ matchPopulateJoins: true,
498
+ type: 'orderBy',
499
+ }));
500
+ });
501
+ return this;
502
+ }
503
+ /** Collect custom aliases from select fields (stored as 'resolved as alias' strings by select()). */
504
+ getSelectAliases() {
505
+ const aliases = new Set();
506
+ for (const field of this.#state.fields ?? []) {
507
+ if (typeof field === 'string') {
508
+ const m = FIELD_ALIAS_RE.exec(field);
509
+ if (m) {
510
+ aliases.add(m[2]);
511
+ }
512
+ }
513
+ }
514
+ return aliases;
515
+ }
516
+ groupBy(fields) {
517
+ this.ensureNotFinalized();
518
+ this.#state.groupBy = Utils.asArray(fields).flatMap(f => {
519
+ if (typeof f !== 'string') {
520
+ // Normalize sql.ref('prop') to string for proper formula resolution
521
+ if (isRaw(f) && f.sql === '??' && f.params.length === 1) {
522
+ return this.resolveNestedPath(String(f.params[0]));
523
+ }
524
+ return f;
525
+ }
526
+ return this.resolveNestedPath(f);
527
+ });
528
+ return this;
529
+ }
530
+ /**
531
+ * Adds a HAVING clause to the query, typically used with GROUP BY.
532
+ *
533
+ * @example
534
+ * ```ts
535
+ * qb.select([raw('count(*) as count'), 'status'])
536
+ * .groupBy('status')
537
+ * .having({ count: { $gt: 5 } });
538
+ * ```
539
+ */
540
+ having(cond = {}, params, operator) {
541
+ this.ensureNotFinalized();
542
+ if (typeof cond === 'string') {
543
+ cond = { [raw(`(${cond})`, params)]: [] };
544
+ }
545
+ const processed = CriteriaNodeFactory.createNode(this.metadata, this.mainAlias.entityName, cond, undefined, undefined, false).process(this, { type: 'having' });
546
+ if (!this.#state.having || !operator) {
547
+ this.#state.having = processed;
548
+ }
549
+ else {
550
+ const cond1 = [this.#state.having, processed];
551
+ this.#state.having = { [operator]: cond1 };
552
+ }
553
+ return this;
554
+ }
555
+ andHaving(cond, params) {
556
+ return this.having(cond, params, '$and');
557
+ }
558
+ orHaving(cond, params) {
559
+ return this.having(cond, params, '$or');
560
+ }
561
+ onConflict(fields = []) {
562
+ const meta = this.mainAlias.meta;
563
+ this.ensureNotFinalized();
564
+ this.#state.onConflict ??= [];
565
+ this.#state.onConflict.push({
566
+ fields: isRaw(fields)
567
+ ? fields
568
+ : Utils.asArray(fields).flatMap(f => {
569
+ const key = f.toString();
570
+ /* v8 ignore next */
571
+ return meta.properties[key]?.fieldNames ?? [key];
572
+ }),
573
+ });
574
+ return this;
575
+ }
576
+ ignore() {
577
+ if (!this.#state.onConflict) {
578
+ throw new Error('You need to call `qb.onConflict()` first to use `qb.ignore()`');
579
+ }
580
+ this.#state.onConflict[this.#state.onConflict.length - 1].ignore = true;
581
+ return this;
582
+ }
583
+ merge(data) {
584
+ if (!this.#state.onConflict) {
585
+ throw new Error('You need to call `qb.onConflict()` first to use `qb.merge()`');
586
+ }
587
+ if (Array.isArray(data) && data.length === 0) {
588
+ return this.ignore();
589
+ }
590
+ this.#state.onConflict[this.#state.onConflict.length - 1].merge = data;
591
+ return this;
592
+ }
593
+ returning(fields) {
594
+ this.#state.returning = Utils.asArray(fields);
595
+ return this;
596
+ }
597
+ /**
598
+ * @internal
599
+ */
600
+ populate(populate, populateWhere, populateFilter) {
601
+ this.ensureNotFinalized();
602
+ this.#state.populate = populate;
603
+ this.#state.populateWhere = populateWhere;
604
+ this.#state.populateFilter = populateFilter;
605
+ return this;
606
+ }
607
+ /**
608
+ * Sets a LIMIT clause to restrict the number of results.
609
+ *
610
+ * @example
611
+ * ```ts
612
+ * qb.select('*').limit(10); // First 10 results
613
+ * qb.select('*').limit(10, 20); // 10 results starting from offset 20
614
+ * ```
615
+ */
616
+ limit(limit, offset = 0) {
617
+ this.ensureNotFinalized();
618
+ this.#state.limit = limit;
619
+ if (offset) {
620
+ this.offset(offset);
621
+ }
622
+ return this;
623
+ }
624
+ /**
625
+ * Sets an OFFSET clause to skip a number of results.
626
+ *
627
+ * @example
628
+ * ```ts
629
+ * qb.select('*').limit(10).offset(20); // Results 21-30
630
+ * ```
631
+ */
632
+ offset(offset) {
633
+ this.ensureNotFinalized();
634
+ this.#state.offset = offset;
635
+ return this;
636
+ }
637
+ withSchema(schema) {
638
+ this.ensureNotFinalized();
639
+ this.#state.schema = schema;
640
+ return this;
641
+ }
642
+ setLockMode(mode, tables) {
643
+ this.ensureNotFinalized();
644
+ if (mode != null && ![LockMode.OPTIMISTIC, LockMode.NONE].includes(mode) && !this.context) {
645
+ throw ValidationError.transactionRequired();
646
+ }
647
+ this.#state.lockMode = mode;
648
+ this.#state.lockTables = tables;
649
+ return this;
650
+ }
651
+ setFlushMode(flushMode) {
652
+ this.ensureNotFinalized();
653
+ this.#state.flushMode = flushMode;
654
+ return this;
655
+ }
656
+ setFlag(flag) {
657
+ this.ensureNotFinalized();
658
+ this.#state.flags.add(flag);
659
+ return this;
660
+ }
661
+ unsetFlag(flag) {
662
+ this.ensureNotFinalized();
663
+ this.#state.flags.delete(flag);
664
+ return this;
665
+ }
666
+ hasFlag(flag) {
667
+ return this.#state.flags.has(flag);
668
+ }
669
+ cache(config = true) {
670
+ this.ensureNotFinalized();
671
+ this.#state.cache = config;
672
+ return this;
673
+ }
674
+ /**
675
+ * Adds index hint to the FROM clause.
676
+ */
677
+ indexHint(sql) {
678
+ this.ensureNotFinalized();
679
+ this.#state.indexHint = sql;
680
+ return this;
681
+ }
682
+ /**
683
+ * Adds COLLATE clause to ORDER BY expressions.
684
+ */
685
+ collation(collation) {
686
+ this.ensureNotFinalized();
687
+ this.#state.collation = collation;
688
+ return this;
689
+ }
690
+ /**
691
+ * Prepend comment to the sql query using the syntax `/* ... *&#8205;/`. Some characters are forbidden such as `/*, *&#8205;/` and `?`.
692
+ */
693
+ comment(comment) {
694
+ this.ensureNotFinalized();
695
+ this.#state.comments.push(...Utils.asArray(comment));
696
+ return this;
697
+ }
698
+ /**
699
+ * Add hints to the query using comment-like syntax `/*+ ... *&#8205;/`. MySQL and Oracle use this syntax for optimizer hints.
700
+ * Also various DB proxies and routers use this syntax to pass hints to alter their behavior. In other dialects the hints
701
+ * are ignored as simple comments.
702
+ */
703
+ hintComment(comment) {
704
+ this.ensureNotFinalized();
705
+ this.#state.hintComments.push(...Utils.asArray(comment));
706
+ return this;
707
+ }
708
+ from(target, aliasName) {
709
+ this.ensureNotFinalized();
710
+ if (target instanceof _a) {
711
+ this.fromSubQuery(target, aliasName);
712
+ }
713
+ else if (typeof target === 'string' && !this.metadata.find(target)) {
714
+ this.fromRawTable(target, aliasName);
715
+ }
716
+ else {
717
+ if (aliasName && this.#state.mainAlias && Utils.className(target) !== this.#state.mainAlias.aliasName) {
718
+ throw new Error(`Cannot override the alias to '${aliasName}' since a query already contains references to '${this.#state.mainAlias.aliasName}'`);
719
+ }
720
+ this.fromEntityName(target, aliasName);
721
+ }
722
+ return this;
723
+ }
724
+ getNativeQuery(processVirtualEntity = true) {
725
+ if (this.#state.unionQuery) {
726
+ if (!this.#query?.qb) {
727
+ this.#query = {};
728
+ const nqb = this.platform.createNativeQueryBuilder();
729
+ nqb.select('*');
730
+ nqb.from(raw(`(${this.#state.unionQuery.sql})`, this.#state.unionQuery.params));
731
+ this.#query.qb = nqb;
732
+ }
733
+ return this.#query.qb;
734
+ }
735
+ if (this.#query?.qb) {
736
+ return this.#query.qb;
737
+ }
752
738
  this.#query = {};
753
- const nqb = this.platform.createNativeQueryBuilder();
754
- nqb.select('*');
755
- nqb.from(raw(`(${this.#state.unionQuery.sql})`, this.#state.unionQuery.params));
756
- this.#query.qb = nqb;
757
- }
758
- return this.#query.qb;
759
- }
760
- if (this.#query?.qb) {
761
- return this.#query.qb;
762
- }
763
- this.#query = {};
764
- this.finalize();
765
- const qb = this.getQueryBase(processVirtualEntity);
766
- for (const cte of this.#state.ctes) {
767
- const query = cte.query;
768
- const opts = { columns: cte.columns, materialized: cte.materialized };
769
- if (cte.recursive) {
770
- qb.withRecursive(cte.name, query, opts);
771
- } else {
772
- qb.with(cte.name, query, opts);
773
- }
774
- }
775
- const schema = this.getSchema(this.mainAlias);
776
- const isNotEmptyObject = obj => Utils.hasObjectKeys(obj) || RawQueryFragment.hasObjectFragments(obj);
777
- Utils.runIfNotEmpty(
778
- () => this.helper.appendQueryCondition(this.type, this.#state.cond, qb),
779
- this.#state.cond && !this.#state.onConflict,
780
- );
781
- Utils.runIfNotEmpty(
782
- () => qb.groupBy(this.prepareFields(this.#state.groupBy, 'groupBy', schema)),
783
- isNotEmptyObject(this.#state.groupBy),
784
- );
785
- Utils.runIfNotEmpty(
786
- () => this.helper.appendQueryCondition(this.type, this.#state.having, qb, undefined, 'having'),
787
- isNotEmptyObject(this.#state.having),
788
- );
789
- Utils.runIfNotEmpty(() => {
790
- const queryOrder = this.helper.getQueryOrder(
791
- this.type,
792
- this.#state.orderBy,
793
- this.#state.populateMap,
794
- this.#state.collation,
795
- );
796
- if (queryOrder.length > 0) {
797
- const sql = Utils.unique(queryOrder).join(', ');
798
- qb.orderBy(sql);
799
- return;
800
- }
801
- }, isNotEmptyObject(this.#state.orderBy));
802
- Utils.runIfNotEmpty(() => qb.limit(this.#state.limit), this.#state.limit != null);
803
- Utils.runIfNotEmpty(() => qb.offset(this.#state.offset), this.#state.offset);
804
- Utils.runIfNotEmpty(() => qb.comment(this.#state.comments), this.#state.comments);
805
- Utils.runIfNotEmpty(() => qb.hintComment(this.#state.hintComments), this.#state.hintComments);
806
- Utils.runIfNotEmpty(
807
- () => this.helper.appendOnConflictClause(QueryType.UPSERT, this.#state.onConflict, qb),
808
- this.#state.onConflict,
809
- );
810
- if (this.#state.lockMode) {
811
- this.helper.getLockSQL(qb, this.#state.lockMode, this.#state.lockTables, this.#state.joins);
812
- }
813
- this.processReturningStatement(qb, this.mainAlias.meta, this.#state.data, this.#state.returning);
814
- return (this.#query.qb = qb);
815
- }
816
- processReturningStatement(qb, meta, data, returning) {
817
- const usesReturningStatement = this.platform.usesReturningStatement() || this.platform.usesOutputStatement();
818
- if (!meta || !data || !usesReturningStatement) {
819
- return;
820
- }
821
- // always respect explicit returning hint
822
- if (returning && returning.length > 0) {
823
- qb.returning(returning.map(field => this.helper.mapper(field, this.type)));
824
- return;
825
- }
826
- if (this.type === QueryType.INSERT) {
827
- const returningProps = meta.hydrateProps
828
- .filter(
829
- prop =>
830
- prop.returning || (prop.persist !== false && ((prop.primary && prop.autoincrement) || prop.defaultRaw)),
831
- )
832
- .filter(prop => !(prop.name in data));
833
- if (returningProps.length > 0) {
834
- qb.returning(Utils.flatten(returningProps.map(prop => prop.fieldNames)));
835
- }
836
- return;
837
- }
838
- if (this.type === QueryType.UPDATE) {
839
- const returningProps = meta.hydrateProps.filter(prop => prop.fieldNames && isRaw(data[prop.fieldNames[0]]));
840
- if (returningProps.length > 0) {
841
- qb.returning(
842
- returningProps.flatMap(prop => {
843
- if (prop.hasConvertToJSValueSQL) {
844
- const aliased = this.platform.quoteIdentifier(prop.fieldNames[0]);
845
- const sql =
846
- prop.customType.convertToJSValueSQL(aliased, this.platform) +
847
- ' as ' +
848
- this.platform.quoteIdentifier(prop.fieldNames[0]);
849
- return [raw(sql)];
739
+ this.finalize();
740
+ const qb = this.getQueryBase(processVirtualEntity);
741
+ for (const cte of this.#state.ctes) {
742
+ const query = cte.query;
743
+ const opts = { columns: cte.columns, materialized: cte.materialized };
744
+ if (cte.recursive) {
745
+ qb.withRecursive(cte.name, query, opts);
850
746
  }
851
- return prop.fieldNames;
852
- }),
853
- );
854
- }
855
- }
856
- }
857
- /**
858
- * Returns the query with parameters as wildcards.
859
- */
860
- getQuery() {
861
- return this.toQuery().sql;
862
- }
863
- /**
864
- * Returns raw fragment representation of this QueryBuilder.
865
- */
866
- toRaw() {
867
- const { sql, params } = this.toQuery();
868
- return raw(sql, params);
869
- }
870
- toQuery() {
871
- if (this.#state.unionQuery) {
872
- return this.#state.unionQuery;
873
- }
874
- if (this.#query?.sql) {
875
- return { sql: this.#query.sql, params: this.#query.params };
876
- }
877
- const query = this.getNativeQuery().compile();
878
- this.#query.sql = query.sql;
879
- this.#query.params = query.params;
880
- return { sql: this.#query.sql, params: this.#query.params };
881
- }
882
- /**
883
- * Returns the list of all parameters for this query.
884
- */
885
- getParams() {
886
- return this.toQuery().params;
887
- }
888
- /**
889
- * Returns raw interpolated query string with all the parameters inlined.
890
- */
891
- getFormattedQuery() {
892
- const query = this.toQuery();
893
- return this.platform.formatQuery(query.sql, query.params);
894
- }
895
- /**
896
- * @internal
897
- */
898
- getAliasForJoinPath(path, options) {
899
- if (!path || path === Utils.className(this.mainAlias.entityName)) {
900
- return this.mainAlias.aliasName;
901
- }
902
- const join = typeof path === 'string' ? this.getJoinForPath(path, options) : path;
903
- if (join?.path?.endsWith('[pivot]')) {
904
- return join.alias;
905
- }
906
- return join?.inverseAlias || join?.alias;
907
- }
908
- /**
909
- * @internal
910
- */
911
- getJoinForPath(path, options) {
912
- const joins = Object.values(this.#state.joins);
913
- if (joins.length === 0) {
914
- return undefined;
915
- }
916
- let join = joins.find(j => j.path === path);
917
- if (options?.preferNoBranch) {
918
- join = joins.find(j => {
919
- return j.path?.replace(/\[\d+]|\[populate]/g, '') === path.replace(/\[\d+]|\[populate]/g, '');
920
- });
921
- }
922
- if (!join && options?.ignoreBranching) {
923
- join = joins.find(j => {
924
- return j.path?.replace(/\[\d+]/g, '') === path.replace(/\[\d+]/g, '');
925
- });
926
- }
927
- if (!join && options?.matchPopulateJoins && options?.ignoreBranching) {
928
- join = joins.find(j => {
929
- return j.path?.replace(/\[\d+]|\[populate]/g, '') === path.replace(/\[\d+]|\[populate]/g, '');
930
- });
931
- }
932
- if (!join && options?.matchPopulateJoins) {
933
- join = joins.find(j => {
934
- return j.path?.replace(/\[populate]/g, '') === path.replace(/\[populate]/g, '');
935
- });
936
- }
937
- return join;
938
- }
939
- /**
940
- * @internal
941
- */
942
- getNextAlias(entityName = 'e') {
943
- entityName = Utils.className(entityName);
944
- return this.driver.config.getNamingStrategy().aliasName(entityName, this.#state.aliasCounter++);
945
- }
946
- /**
947
- * Registers a join for a specific polymorphic target type.
948
- * Used by the driver to create per-target LEFT JOINs for JOINED loading.
949
- * @internal
950
- */
951
- addPolymorphicJoin(prop, targetMeta, ownerAlias, alias, type, path, schema) {
952
- // Override referencedColumnNames to use the specific target's PK columns
953
- // (polymorphic targets may have different PK column names, e.g. org_id vs user_id)
954
- const referencedColumnNames = targetMeta.getPrimaryProps().flatMap(pk => pk.fieldNames);
955
- const targetProp = { ...prop, targetMeta, referencedColumnNames };
956
- const aliasedName = `${ownerAlias}.${prop.name}[${targetMeta.className}]#${alias}`;
957
- this.#state.joins[aliasedName] = this.helper.joinManyToOneReference(
958
- targetProp,
959
- ownerAlias,
960
- alias,
961
- type,
962
- {},
963
- schema,
964
- );
965
- this.#state.joins[aliasedName].path = path;
966
- this.createAlias(targetMeta.class, alias);
967
- }
968
- /**
969
- * @internal
970
- */
971
- getAliasMap() {
972
- return Object.fromEntries(Object.entries(this.#state.aliases).map(([key, value]) => [key, value.entityName]));
973
- }
974
- /**
975
- * Executes this QB and returns the raw results, mapped to the property names (unless disabled via last parameter).
976
- * Use `method` to specify what kind of result you want to get (array/single/meta).
977
- */
978
- async execute(method, options) {
979
- options = typeof options === 'boolean' ? { mapResults: options } : (options ?? {});
980
- options.mergeResults ??= true;
981
- options.mapResults ??= true;
982
- const isRunType = [QueryType.INSERT, QueryType.UPDATE, QueryType.DELETE, QueryType.TRUNCATE].includes(this.type);
983
- method ??= isRunType ? 'run' : 'all';
984
- if (!this.connectionType && (isRunType || this.context)) {
985
- this.connectionType = 'write';
986
- }
987
- if (!this.#state.finalized && method === 'get' && this.type === QueryType.SELECT) {
988
- this.limit(1);
989
- }
990
- const query = this.toQuery();
991
- const cached = await this.em?.tryCache(this.mainAlias.entityName, this.#state.cache, [
992
- 'qb.execute',
993
- query.sql,
994
- query.params,
995
- method,
996
- ]);
997
- if (cached?.data !== undefined) {
998
- return cached.data;
999
- }
1000
- const loggerContext = { id: this.em?.id, ...this.loggerContext };
1001
- const res = await this.getConnection().execute(query.sql, query.params, method, this.context, loggerContext);
1002
- const meta = this.mainAlias.meta;
1003
- if (!options.mapResults || !meta) {
1004
- await this.em?.storeCache(this.#state.cache, cached, res);
1005
- return res;
1006
- }
1007
- if (method === 'run') {
1008
- return res;
1009
- }
1010
- const joinedProps = this.driver.joinedProps(meta, this.#state.populate);
1011
- let mapped;
1012
- if (Array.isArray(res)) {
1013
- const map = {};
1014
- mapped = res.map(r => this.driver.mapResult(r, meta, this.#state.populate, this, map));
1015
- if (options.mergeResults && joinedProps.length > 0) {
1016
- mapped = this.driver.mergeJoinedResult(mapped, this.mainAlias.meta, joinedProps);
1017
- }
1018
- } else {
1019
- mapped = [this.driver.mapResult(res, meta, joinedProps, this)];
1020
- }
1021
- if (method === 'get') {
1022
- await this.em?.storeCache(this.#state.cache, cached, mapped[0]);
1023
- return mapped[0];
1024
- }
1025
- await this.em?.storeCache(this.#state.cache, cached, mapped);
1026
- return mapped;
1027
- }
1028
- getConnection() {
1029
- const write = !this.platform.getConfig().get('preferReadReplicas');
1030
- const type = this.connectionType || (write ? 'write' : 'read');
1031
- return this.driver.getConnection(type);
1032
- }
1033
- /**
1034
- * Executes the query and returns an async iterable (async generator) that yields results one by one.
1035
- * By default, the results are merged and mapped to entity instances, without adding them to the identity map.
1036
- * You can disable merging and mapping by passing the options `{ mergeResults: false, mapResults: false }`.
1037
- * This is useful for processing large datasets without loading everything into memory at once.
1038
- *
1039
- * ```ts
1040
- * const qb = em.createQueryBuilder(Book, 'b');
1041
- * qb.select('*').where({ title: '1984' }).leftJoinAndSelect('b.author', 'a');
1042
- *
1043
- * for await (const book of qb.stream()) {
1044
- * // book is an instance of Book entity
1045
- * console.log(book.title, book.author.name);
1046
- * }
1047
- * ```
1048
- */
1049
- async *stream(options) {
1050
- options ??= {};
1051
- options.mergeResults ??= true;
1052
- options.mapResults ??= true;
1053
- const query = this.toQuery();
1054
- const loggerContext = { id: this.em?.id, ...this.loggerContext };
1055
- const res = this.getConnection().stream(query.sql, query.params, this.context, loggerContext);
1056
- const meta = this.mainAlias.meta;
1057
- if (options.rawResults || !meta) {
1058
- yield* res;
1059
- return;
1060
- }
1061
- const joinedProps = this.driver.joinedProps(meta, this.#state.populate);
1062
- const stack = [];
1063
- const hash = data => {
1064
- return Utils.getPrimaryKeyHash(meta.primaryKeys.map(pk => data[pk]));
1065
- };
1066
- for await (const row of res) {
1067
- const mapped = this.driver.mapResult(row, meta, this.#state.populate, this);
1068
- if (!options.mergeResults || joinedProps.length === 0) {
1069
- yield this.mapResult(mapped, options.mapResults);
1070
- continue;
1071
- }
1072
- if (stack.length > 0 && hash(stack[stack.length - 1]) !== hash(mapped)) {
1073
- const res = this.driver.mergeJoinedResult(stack, this.mainAlias.meta, joinedProps);
1074
- for (const row of res) {
1075
- yield this.mapResult(row, options.mapResults);
1076
- }
1077
- stack.length = 0;
1078
- }
1079
- stack.push(mapped);
1080
- }
1081
- if (stack.length > 0) {
1082
- const merged = this.driver.mergeJoinedResult(stack, this.mainAlias.meta, joinedProps);
1083
- yield this.mapResult(merged[0], options.mapResults);
1084
- }
1085
- }
1086
- /**
1087
- * Alias for `qb.getResultList()`
1088
- */
1089
- async getResult() {
1090
- return this.getResultList();
1091
- }
1092
- /**
1093
- * Executes the query, returning array of results mapped to entity instances.
1094
- */
1095
- async getResultList(limit) {
1096
- await this.em.tryFlush(this.mainAlias.entityName, { flushMode: this.#state.flushMode });
1097
- const res = await this.execute('all', true);
1098
- return this.mapResults(res, limit);
1099
- }
1100
- propagatePopulateHint(entity, hint) {
1101
- helper(entity).__serializationContext.populate = hint.concat(helper(entity).__serializationContext.populate ?? []);
1102
- hint.forEach(hint => {
1103
- const [propName] = hint.field.split(':', 2);
1104
- const value = Reference.unwrapReference(entity[propName]);
1105
- if (Utils.isEntity(value)) {
1106
- this.propagatePopulateHint(value, hint.children ?? []);
1107
- } else if (Utils.isCollection(value)) {
1108
- value.populated();
1109
- value.getItems(false).forEach(item => this.propagatePopulateHint(item, hint.children ?? []));
1110
- }
1111
- });
1112
- }
1113
- mapResult(row, map = true) {
1114
- if (!map) {
1115
- return row;
1116
- }
1117
- const entity = this.em.map(this.mainAlias.entityName, row, { schema: this.#state.schema });
1118
- this.propagatePopulateHint(entity, this.#state.populate);
1119
- return entity;
1120
- }
1121
- mapResults(res, limit) {
1122
- const entities = [];
1123
- for (const row of res) {
1124
- const entity = this.mapResult(row);
1125
- this.propagatePopulateHint(entity, this.#state.populate);
1126
- entities.push(entity);
1127
- if (limit != null && --limit === 0) {
1128
- break;
1129
- }
1130
- }
1131
- return Utils.unique(entities);
1132
- }
1133
- /**
1134
- * Executes the query, returning the first result or null
1135
- */
1136
- async getSingleResult() {
1137
- if (!this.#state.finalized) {
1138
- this.limit(1);
1139
- }
1140
- const [res] = await this.getResultList(1);
1141
- return res || null;
1142
- }
1143
- async getCount(field, distinct) {
1144
- let res;
1145
- if (this.type === QueryType.COUNT) {
1146
- res = await this.execute('get', false);
1147
- } else {
1148
- const qb = this.#state.type === undefined ? this : this.clone();
1149
- qb.processPopulateHint(); // needs to happen sooner so `qb.hasToManyJoins()` reports correctly
1150
- qb.count(field, distinct ?? qb.hasToManyJoins())
1151
- .limit(undefined)
1152
- .offset(undefined)
1153
- .orderBy([]);
1154
- res = await qb.execute('get', false);
1155
- }
1156
- return res ? +res.count : 0;
1157
- }
1158
- /**
1159
- * Executes the query, returning both array of results and total count query (without offset and limit).
1160
- */
1161
- async getResultAndCount() {
1162
- return [await this.clone().getResultList(), await this.clone().getCount()];
1163
- }
1164
- as(aliasOrTargetEntity, alias) {
1165
- const qb = this.getNativeQuery();
1166
- let finalAlias = aliasOrTargetEntity;
1167
- /* v8 ignore next */
1168
- if (typeof aliasOrTargetEntity === 'string' && aliasOrTargetEntity.includes('.')) {
1169
- throw new Error(
1170
- 'qb.as(alias) no longer supports target entity name prefix, use qb.as(TargetEntity, key) signature instead',
1171
- );
1172
- }
1173
- if (alias) {
1174
- const meta = this.metadata.get(aliasOrTargetEntity);
1175
- /* v8 ignore next */
1176
- finalAlias = meta.properties[alias]?.fieldNames[0] ?? alias;
1177
- }
1178
- qb.as(finalAlias);
1179
- // tag the instance, so it is possible to detect it easily
1180
- Object.defineProperty(qb, '__as', { enumerable: false, value: finalAlias });
1181
- return qb;
1182
- }
1183
- /**
1184
- * Combines the current query with one or more other queries using `UNION ALL`.
1185
- * All queries must select the same columns. Returns a `QueryBuilder` that
1186
- * can be used with `$in`, passed to `qb.from()`, or converted via `.getQuery()`,
1187
- * `.getParams()`, `.toQuery()`, `.toRaw()`, etc.
1188
- *
1189
- * ```ts
1190
- * const qb1 = em.createQueryBuilder(Employee).select('id').where(condition1);
1191
- * const qb2 = em.createQueryBuilder(Employee).select('id').where(condition2);
1192
- * const qb3 = em.createQueryBuilder(Employee).select('id').where(condition3);
1193
- * const subquery = qb1.unionAll(qb2, qb3);
1194
- *
1195
- * const results = await em.find(Employee, { id: { $in: subquery } });
1196
- * ```
1197
- */
1198
- unionAll(...others) {
1199
- return this.buildUnionQuery('union all', others);
1200
- }
1201
- /**
1202
- * Combines the current query with one or more other queries using `UNION` (with deduplication).
1203
- * All queries must select the same columns. Returns a `QueryBuilder` that
1204
- * can be used with `$in`, passed to `qb.from()`, or converted via `.getQuery()`,
1205
- * `.getParams()`, `.toQuery()`, `.toRaw()`, etc.
1206
- *
1207
- * ```ts
1208
- * const qb1 = em.createQueryBuilder(Employee).select('id').where(condition1);
1209
- * const qb2 = em.createQueryBuilder(Employee).select('id').where(condition2);
1210
- * const subquery = qb1.union(qb2);
1211
- *
1212
- * const results = await em.find(Employee, { id: { $in: subquery } });
1213
- * ```
1214
- */
1215
- union(...others) {
1216
- return this.buildUnionQuery('union', others);
1217
- }
1218
- buildUnionQuery(separator, others) {
1219
- const all = [this, ...others];
1220
- const parts = [];
1221
- const params = [];
1222
- for (const qb of all) {
1223
- const compiled = qb instanceof _a ? qb.toQuery() : qb.compile();
1224
- parts.push(`(${compiled.sql})`);
1225
- params.push(...compiled.params);
1226
- }
1227
- const result = this.clone(true);
1228
- result.#state.unionQuery = { sql: parts.join(` ${separator} `), params };
1229
- return result;
1230
- }
1231
- with(name, query, options) {
1232
- return this.addCte(name, query, options);
1233
- }
1234
- withRecursive(name, query, options) {
1235
- return this.addCte(name, query, options, true);
1236
- }
1237
- addCte(name, query, options, recursive) {
1238
- this.ensureNotFinalized();
1239
- if (this.#state.ctes.some(cte => cte.name === name)) {
1240
- throw new Error(`CTE with name '${name}' already exists`);
1241
- }
1242
- // Eagerly compile QueryBuilder to RawQueryFragment — later mutations to the sub-query won't be reflected
1243
- const compiled = query instanceof _a ? query.toRaw() : query;
1244
- this.#state.ctes.push({
1245
- name,
1246
- query: compiled,
1247
- recursive,
1248
- columns: options?.columns,
1249
- materialized: options?.materialized,
1250
- });
1251
- return this;
1252
- }
1253
- clone(reset, preserve) {
1254
- const qb = new _a(
1255
- this.#state.mainAlias.entityName,
1256
- this.metadata,
1257
- this.driver,
1258
- this.context,
1259
- this.#state.mainAlias.aliasName,
1260
- this.connectionType,
1261
- this.em,
1262
- );
1263
- if (reset !== true) {
1264
- qb.#state = Utils.copy(this.#state);
1265
- // CTEs contain NativeQueryBuilder instances that should not be deep-cloned
1266
- qb.#state.ctes = this.#state.ctes.map(cte => ({ ...cte }));
1267
- if (Array.isArray(reset)) {
1268
- const fresh = _a.createDefaultState();
1269
- for (const key of reset) {
1270
- qb.#state[key] = fresh[key];
1271
- }
1272
- }
1273
- } else if (preserve) {
1274
- for (const key of preserve) {
1275
- qb.#state[key] = Utils.copy(this.#state[key]);
1276
- }
1277
- }
1278
- qb.#state.finalized = false;
1279
- qb.#query = undefined;
1280
- qb.#helper = qb.createQueryBuilderHelper();
1281
- return qb;
1282
- }
1283
- /**
1284
- * Sets logger context for this query builder.
1285
- */
1286
- setLoggerContext(context) {
1287
- this.loggerContext = context;
1288
- }
1289
- /**
1290
- * Gets logger context for this query builder.
1291
- */
1292
- getLoggerContext() {
1293
- this.loggerContext ??= {};
1294
- return this.loggerContext;
1295
- }
1296
- fromVirtual(meta) {
1297
- if (typeof meta.expression === 'string') {
1298
- return `(${meta.expression}) as ${this.platform.quoteIdentifier(this.alias)}`;
1299
- }
1300
- const res = meta.expression(this.em, this.#state.cond, {});
1301
- if (typeof res === 'string') {
1302
- return `(${res}) as ${this.platform.quoteIdentifier(this.alias)}`;
1303
- }
1304
- if (res instanceof _a) {
1305
- return `(${res.getFormattedQuery()}) as ${this.platform.quoteIdentifier(this.alias)}`;
1306
- }
1307
- if (isRaw(res)) {
1308
- const query = this.platform.formatQuery(res.sql, res.params);
1309
- return `(${query}) as ${this.platform.quoteIdentifier(this.alias)}`;
747
+ else {
748
+ qb.with(cte.name, query, opts);
749
+ }
750
+ }
751
+ const schema = this.getSchema(this.mainAlias);
752
+ const isNotEmptyObject = (obj) => Utils.hasObjectKeys(obj) || RawQueryFragment.hasObjectFragments(obj);
753
+ Utils.runIfNotEmpty(() => this.helper.appendQueryCondition(this.type, this.#state.cond, qb), this.#state.cond && !this.#state.onConflict);
754
+ Utils.runIfNotEmpty(() => qb.groupBy(this.prepareFields(this.#state.groupBy, 'groupBy', schema)), isNotEmptyObject(this.#state.groupBy));
755
+ Utils.runIfNotEmpty(() => this.helper.appendQueryCondition(this.type, this.#state.having, qb, undefined, 'having'), isNotEmptyObject(this.#state.having));
756
+ Utils.runIfNotEmpty(() => {
757
+ const queryOrder = this.helper.getQueryOrder(this.type, this.#state.orderBy, this.#state.populateMap, this.#state.collation);
758
+ if (queryOrder.length > 0) {
759
+ const sql = Utils.unique(queryOrder).join(', ');
760
+ qb.orderBy(sql);
761
+ return;
762
+ }
763
+ }, isNotEmptyObject(this.#state.orderBy));
764
+ Utils.runIfNotEmpty(() => qb.limit(this.#state.limit), this.#state.limit != null);
765
+ Utils.runIfNotEmpty(() => qb.offset(this.#state.offset), this.#state.offset);
766
+ Utils.runIfNotEmpty(() => qb.comment(this.#state.comments), this.#state.comments);
767
+ Utils.runIfNotEmpty(() => qb.hintComment(this.#state.hintComments), this.#state.hintComments);
768
+ Utils.runIfNotEmpty(() => this.helper.appendOnConflictClause(QueryType.UPSERT, this.#state.onConflict, qb), this.#state.onConflict);
769
+ if (this.#state.lockMode) {
770
+ this.helper.getLockSQL(qb, this.#state.lockMode, this.#state.lockTables, this.#state.joins);
771
+ }
772
+ this.processReturningStatement(qb, this.mainAlias.meta, this.#state.data, this.#state.returning);
773
+ return (this.#query.qb = qb);
1310
774
  }
1311
- /* v8 ignore next */
1312
- return res;
1313
- }
1314
- /**
1315
- * Adds a join from a property object. Used internally for TPT joins where the property
1316
- * is synthetic (not in entity.properties) but defined on metadata (e.g., tptParentProp).
1317
- * The caller must create the alias first via createAlias().
1318
- * @internal
1319
- */
1320
- addPropertyJoin(prop, ownerAlias, alias, type, path, schema) {
1321
- schema ??= prop.targetMeta?.schema === '*' ? '*' : this.driver.getSchemaName(prop.targetMeta);
1322
- const key = `[tpt]${ownerAlias}#${alias}`;
1323
- this.#state.joins[key] =
1324
- prop.kind === ReferenceKind.MANY_TO_ONE
1325
- ? this.helper.joinManyToOneReference(prop, ownerAlias, alias, type, {}, schema)
1326
- : this.helper.joinOneToReference(prop, ownerAlias, alias, type, {}, schema);
1327
- this.#state.joins[key].path = path;
1328
- return key;
1329
- }
1330
- joinReference(field, alias, cond, type, path, schema, subquery) {
1331
- this.ensureNotFinalized();
1332
- if (typeof field === 'object') {
1333
- const prop = {
1334
- name: '__subquery__',
1335
- kind: ReferenceKind.MANY_TO_ONE,
1336
- };
1337
- if (field instanceof _a) {
1338
- prop.type = Utils.className(field.mainAlias.entityName);
1339
- prop.targetMeta = field.mainAlias.meta;
1340
- field = field.getNativeQuery();
1341
- }
1342
- if (isRaw(field)) {
1343
- field = this.platform.formatQuery(field.sql, field.params);
1344
- }
1345
- const key = `${this.alias}.${prop.name}#${alias}`;
1346
- this.#state.joins[key] = {
1347
- prop,
1348
- alias,
1349
- type,
1350
- cond,
1351
- schema,
1352
- subquery: field.toString(),
1353
- ownerAlias: this.alias,
1354
- };
1355
- return { prop, key };
1356
- }
1357
- if (!subquery && type.includes('lateral')) {
1358
- throw new Error(`Lateral join can be used only with a sub-query.`);
1359
- }
1360
- const [fromAlias, fromField] = this.helper.splitField(field);
1361
- const q = str => `'${str}'`;
1362
- if (!this.#state.aliases[fromAlias]) {
1363
- throw new Error(
1364
- `Trying to join ${q(fromField)} with alias ${q(fromAlias)}, but ${q(fromAlias)} is not a known alias. Available aliases are: ${Object.keys(this.#state.aliases).map(q).join(', ')}.`,
1365
- );
1366
- }
1367
- const entityName = this.#state.aliases[fromAlias].entityName;
1368
- const meta = this.metadata.get(entityName);
1369
- const prop = meta.properties[fromField];
1370
- if (!prop) {
1371
- throw new Error(
1372
- `Trying to join ${q(field)}, but ${q(fromField)} is not a defined relation on ${meta.className}.`,
1373
- );
1374
- }
1375
- // For TPT inheritance, owning relations (M:1 and owning 1:1) may have FK columns in a parent table
1376
- // Resolve the correct alias for the table that owns the FK column
1377
- const ownerAlias =
1378
- prop.kind === ReferenceKind.MANY_TO_ONE || (prop.kind === ReferenceKind.ONE_TO_ONE && prop.owner)
1379
- ? this.helper.getTPTAliasForProperty(fromField, fromAlias)
1380
- : fromAlias;
1381
- this.createAlias(prop.targetMeta.class, alias);
1382
- cond = QueryHelper.processWhere({
1383
- where: cond,
1384
- entityName: this.mainAlias.entityName,
1385
- metadata: this.metadata,
1386
- platform: this.platform,
1387
- aliasMap: this.getAliasMap(),
1388
- aliased: [QueryType.SELECT, QueryType.COUNT].includes(this.type),
1389
- });
1390
- const criteriaNode = CriteriaNodeFactory.createNode(this.metadata, prop.targetMeta.class, cond);
1391
- cond = criteriaNode.process(this, { ignoreBranching: true, alias });
1392
- let aliasedName = `${fromAlias}.${prop.name}#${alias}`;
1393
- path ??= `${Object.values(this.#state.joins).find(j => j.alias === fromAlias)?.path ?? Utils.className(entityName)}.${prop.name}`;
1394
- if (prop.kind === ReferenceKind.ONE_TO_MANY) {
1395
- this.#state.joins[aliasedName] = this.helper.joinOneToReference(prop, fromAlias, alias, type, cond, schema);
1396
- this.#state.joins[aliasedName].path ??= path;
1397
- } else if (prop.kind === ReferenceKind.MANY_TO_MANY) {
1398
- let pivotAlias = alias;
1399
- if (type !== JoinType.pivotJoin) {
1400
- const oldPivotAlias = this.getAliasForJoinPath(path + '[pivot]');
1401
- pivotAlias = oldPivotAlias ?? this.getNextAlias(prop.pivotEntity);
1402
- aliasedName = `${fromAlias}.${prop.name}#${pivotAlias}`;
1403
- }
1404
- const joins = this.helper.joinManyToManyReference(prop, fromAlias, alias, pivotAlias, type, cond, path, schema);
1405
- Object.assign(this.#state.joins, joins);
1406
- this.createAlias(prop.pivotEntity, pivotAlias);
1407
- this.#state.joins[aliasedName].path ??= path;
1408
- aliasedName = Object.keys(joins)[1];
1409
- } else if (prop.kind === ReferenceKind.ONE_TO_ONE) {
1410
- this.#state.joins[aliasedName] = this.helper.joinOneToReference(prop, ownerAlias, alias, type, cond, schema);
1411
- this.#state.joins[aliasedName].path ??= path;
1412
- } else {
1413
- // MANY_TO_ONE
1414
- this.#state.joins[aliasedName] = this.helper.joinManyToOneReference(prop, ownerAlias, alias, type, cond, schema);
1415
- this.#state.joins[aliasedName].path ??= path;
1416
- }
1417
- return { prop, key: aliasedName };
1418
- }
1419
- prepareFields(fields, type = 'where', schema) {
1420
- const ret = [];
1421
- const getFieldName = (name, customAlias) => {
1422
- const alias = customAlias ?? (type === 'groupBy' ? null : undefined);
1423
- return this.helper.mapper(name, this.type, undefined, alias, schema);
1424
- };
1425
- fields.forEach(originalField => {
1426
- if (typeof originalField !== 'string') {
1427
- ret.push(originalField);
1428
- return;
1429
- }
1430
- // Strip 'as alias' suffix if present — the alias is passed to mapper at the end
1431
- let field = originalField;
1432
- let customAlias;
1433
- const asMatch = FIELD_ALIAS_RE.exec(originalField);
1434
- if (asMatch) {
1435
- field = asMatch[1].trim();
1436
- customAlias = asMatch[2];
1437
- }
1438
- const join = Object.keys(this.#state.joins).find(k => field === k.substring(0, k.indexOf('#')));
1439
- if (join && type === 'where') {
1440
- ret.push(...this.helper.mapJoinColumns(this.type, this.#state.joins[join]));
1441
- return;
1442
- }
1443
- const [a, f] = this.helper.splitField(field);
1444
- const prop = this.helper.getProperty(f, a);
1445
- /* v8 ignore next */
1446
- if (prop && [ReferenceKind.ONE_TO_MANY, ReferenceKind.MANY_TO_MANY].includes(prop.kind)) {
1447
- return;
1448
- }
1449
- if (prop?.persist === false && !prop.embedded && !prop.formula && type === 'where') {
1450
- return;
1451
- }
1452
- if (prop?.embedded || (prop?.kind === ReferenceKind.EMBEDDED && prop.object)) {
1453
- const name = prop.embeddedPath?.join('.') ?? prop.fieldNames[0];
1454
- const aliased = this.#state.aliases[a] ? `${a}.${name}` : name;
1455
- ret.push(getFieldName(aliased, customAlias));
1456
- return;
1457
- }
1458
- if (prop?.kind === ReferenceKind.EMBEDDED) {
1459
- if (customAlias) {
1460
- throw new Error(
1461
- `Cannot use 'as ${customAlias}' alias on embedded property '${field}' because it expands to multiple columns. Alias individual fields instead (e.g. '${field}.propertyName as ${customAlias}').`,
1462
- );
1463
- }
1464
- const nest = prop => {
1465
- for (const childProp of Object.values(prop.embeddedProps)) {
1466
- if (
1467
- childProp.fieldNames &&
1468
- (childProp.kind !== ReferenceKind.EMBEDDED || childProp.object) &&
1469
- childProp.persist !== false
1470
- ) {
1471
- ret.push(getFieldName(childProp.fieldNames[0]));
1472
- } else {
1473
- nest(childProp);
775
+ processReturningStatement(qb, meta, data, returning) {
776
+ const usesReturningStatement = this.platform.usesReturningStatement() || this.platform.usesOutputStatement();
777
+ if (!meta || !data || !usesReturningStatement) {
778
+ return;
779
+ }
780
+ // always respect explicit returning hint
781
+ if (returning && returning.length > 0) {
782
+ qb.returning(returning.map(field => this.helper.mapper(field, this.type)));
783
+ return;
784
+ }
785
+ if (this.type === QueryType.INSERT) {
786
+ const returningProps = meta.hydrateProps
787
+ .filter(prop => prop.returning || (prop.persist !== false && ((prop.primary && prop.autoincrement) || prop.defaultRaw)))
788
+ .filter(prop => !(prop.name in data));
789
+ if (returningProps.length > 0) {
790
+ qb.returning(Utils.flatten(returningProps.map(prop => prop.fieldNames)));
791
+ }
792
+ return;
793
+ }
794
+ if (this.type === QueryType.UPDATE) {
795
+ const returningProps = meta.hydrateProps.filter(prop => prop.fieldNames && isRaw(data[prop.fieldNames[0]]));
796
+ if (returningProps.length > 0) {
797
+ qb.returning(returningProps.flatMap((prop) => {
798
+ if (prop.hasConvertToJSValueSQL) {
799
+ const aliased = this.platform.quoteIdentifier(prop.fieldNames[0]);
800
+ const sql = prop.customType.convertToJSValueSQL(aliased, this.platform) +
801
+ ' as ' +
802
+ this.platform.quoteIdentifier(prop.fieldNames[0]);
803
+ return [raw(sql)];
804
+ }
805
+ return prop.fieldNames;
806
+ }));
1474
807
  }
1475
- }
808
+ }
809
+ }
810
+ /**
811
+ * Returns the query with parameters as wildcards.
812
+ */
813
+ getQuery() {
814
+ return this.toQuery().sql;
815
+ }
816
+ /**
817
+ * Returns raw fragment representation of this QueryBuilder.
818
+ */
819
+ toRaw() {
820
+ const { sql, params } = this.toQuery();
821
+ return raw(sql, params);
822
+ }
823
+ toQuery() {
824
+ if (this.#state.unionQuery) {
825
+ return this.#state.unionQuery;
826
+ }
827
+ if (this.#query?.sql) {
828
+ return { sql: this.#query.sql, params: this.#query.params };
829
+ }
830
+ const query = this.getNativeQuery().compile();
831
+ this.#query.sql = query.sql;
832
+ this.#query.params = query.params;
833
+ return { sql: this.#query.sql, params: this.#query.params };
834
+ }
835
+ /**
836
+ * Returns the list of all parameters for this query.
837
+ */
838
+ getParams() {
839
+ return this.toQuery().params;
840
+ }
841
+ /**
842
+ * Returns raw interpolated query string with all the parameters inlined.
843
+ */
844
+ getFormattedQuery() {
845
+ const query = this.toQuery();
846
+ return this.platform.formatQuery(query.sql, query.params);
847
+ }
848
+ /**
849
+ * @internal
850
+ */
851
+ getAliasForJoinPath(path, options) {
852
+ if (!path || path === Utils.className(this.mainAlias.entityName)) {
853
+ return this.mainAlias.aliasName;
854
+ }
855
+ const join = typeof path === 'string' ? this.getJoinForPath(path, options) : path;
856
+ if (join?.path?.endsWith('[pivot]')) {
857
+ return join.alias;
858
+ }
859
+ return join?.inverseAlias || join?.alias;
860
+ }
861
+ /**
862
+ * @internal
863
+ */
864
+ getJoinForPath(path, options) {
865
+ const joins = Object.values(this.#state.joins);
866
+ if (joins.length === 0) {
867
+ return undefined;
868
+ }
869
+ let join = joins.find(j => j.path === path);
870
+ if (options?.preferNoBranch) {
871
+ join = joins.find(j => {
872
+ return j.path?.replace(/\[\d+]|\[populate]/g, '') === path.replace(/\[\d+]|\[populate]/g, '');
873
+ });
874
+ }
875
+ if (!join && options?.ignoreBranching) {
876
+ join = joins.find(j => {
877
+ return j.path?.replace(/\[\d+]/g, '') === path.replace(/\[\d+]/g, '');
878
+ });
879
+ }
880
+ if (!join && options?.matchPopulateJoins && options?.ignoreBranching) {
881
+ join = joins.find(j => {
882
+ return j.path?.replace(/\[\d+]|\[populate]/g, '') === path.replace(/\[\d+]|\[populate]/g, '');
883
+ });
884
+ }
885
+ if (!join && options?.matchPopulateJoins) {
886
+ join = joins.find(j => {
887
+ return j.path?.replace(/\[populate]/g, '') === path.replace(/\[populate]/g, '');
888
+ });
889
+ }
890
+ return join;
891
+ }
892
+ /**
893
+ * @internal
894
+ */
895
+ getNextAlias(entityName = 'e') {
896
+ entityName = Utils.className(entityName);
897
+ return this.driver.config.getNamingStrategy().aliasName(entityName, this.#state.aliasCounter++);
898
+ }
899
+ /**
900
+ * Registers a join for a specific polymorphic target type.
901
+ * Used by the driver to create per-target LEFT JOINs for JOINED loading.
902
+ * @internal
903
+ */
904
+ addPolymorphicJoin(prop, targetMeta, ownerAlias, alias, type, path, schema) {
905
+ // Override referencedColumnNames to use the specific target's PK columns
906
+ // (polymorphic targets may have different PK column names, e.g. org_id vs user_id)
907
+ const referencedColumnNames = targetMeta.getPrimaryProps().flatMap(pk => pk.fieldNames);
908
+ const targetProp = { ...prop, targetMeta, referencedColumnNames };
909
+ const aliasedName = `${ownerAlias}.${prop.name}[${targetMeta.className}]#${alias}`;
910
+ this.#state.joins[aliasedName] = this.helper.joinManyToOneReference(targetProp, ownerAlias, alias, type, {}, schema);
911
+ this.#state.joins[aliasedName].path = path;
912
+ this.createAlias(targetMeta.class, alias);
913
+ }
914
+ /**
915
+ * @internal
916
+ */
917
+ getAliasMap() {
918
+ return Object.fromEntries(Object.entries(this.#state.aliases).map(([key, value]) => [key, value.entityName]));
919
+ }
920
+ /**
921
+ * Executes this QB and returns the raw results, mapped to the property names (unless disabled via last parameter).
922
+ * Use `method` to specify what kind of result you want to get (array/single/meta).
923
+ */
924
+ async execute(method, options) {
925
+ options = typeof options === 'boolean' ? { mapResults: options } : (options ?? {});
926
+ options.mergeResults ??= true;
927
+ options.mapResults ??= true;
928
+ const isRunType = [QueryType.INSERT, QueryType.UPDATE, QueryType.DELETE, QueryType.TRUNCATE].includes(this.type);
929
+ method ??= isRunType ? 'run' : 'all';
930
+ if (!this.connectionType && (isRunType || this.context)) {
931
+ this.connectionType = 'write';
932
+ }
933
+ if (!this.#state.finalized && method === 'get' && this.type === QueryType.SELECT) {
934
+ this.limit(1);
935
+ }
936
+ const query = this.toQuery();
937
+ const cached = await this.em?.tryCache(this.mainAlias.entityName, this.#state.cache, [
938
+ 'qb.execute',
939
+ query.sql,
940
+ query.params,
941
+ method,
942
+ ]);
943
+ if (cached?.data !== undefined) {
944
+ return cached.data;
945
+ }
946
+ const loggerContext = { id: this.em?.id, ...this.loggerContext };
947
+ const res = await this.getConnection().execute(query.sql, query.params, method, this.context, loggerContext);
948
+ const meta = this.mainAlias.meta;
949
+ if (!options.mapResults || !meta) {
950
+ await this.em?.storeCache(this.#state.cache, cached, res);
951
+ return res;
952
+ }
953
+ if (method === 'run') {
954
+ return res;
955
+ }
956
+ const joinedProps = this.driver.joinedProps(meta, this.#state.populate);
957
+ let mapped;
958
+ if (Array.isArray(res)) {
959
+ const map = {};
960
+ mapped = res.map(r => this.driver.mapResult(r, meta, this.#state.populate, this, map));
961
+ if (options.mergeResults && joinedProps.length > 0) {
962
+ mapped = this.driver.mergeJoinedResult(mapped, this.mainAlias.meta, joinedProps);
963
+ }
964
+ }
965
+ else {
966
+ mapped = [this.driver.mapResult(res, meta, joinedProps, this)];
967
+ }
968
+ if (method === 'get') {
969
+ await this.em?.storeCache(this.#state.cache, cached, mapped[0]);
970
+ return mapped[0];
971
+ }
972
+ await this.em?.storeCache(this.#state.cache, cached, mapped);
973
+ return mapped;
974
+ }
975
+ getConnection() {
976
+ const write = !this.platform.getConfig().get('preferReadReplicas');
977
+ const type = this.connectionType || (write ? 'write' : 'read');
978
+ return this.driver.getConnection(type);
979
+ }
980
+ /**
981
+ * Executes the query and returns an async iterable (async generator) that yields results one by one.
982
+ * By default, the results are merged and mapped to entity instances, without adding them to the identity map.
983
+ * You can disable merging and mapping by passing the options `{ mergeResults: false, mapResults: false }`.
984
+ * This is useful for processing large datasets without loading everything into memory at once.
985
+ *
986
+ * ```ts
987
+ * const qb = em.createQueryBuilder(Book, 'b');
988
+ * qb.select('*').where({ title: '1984' }).leftJoinAndSelect('b.author', 'a');
989
+ *
990
+ * for await (const book of qb.stream()) {
991
+ * // book is an instance of Book entity
992
+ * console.log(book.title, book.author.name);
993
+ * }
994
+ * ```
995
+ */
996
+ async *stream(options) {
997
+ options ??= {};
998
+ options.mergeResults ??= true;
999
+ options.mapResults ??= true;
1000
+ const query = this.toQuery();
1001
+ const loggerContext = { id: this.em?.id, ...this.loggerContext };
1002
+ const res = this.getConnection().stream(query.sql, query.params, this.context, loggerContext);
1003
+ const meta = this.mainAlias.meta;
1004
+ if (options.rawResults || !meta) {
1005
+ yield* res;
1006
+ return;
1007
+ }
1008
+ const joinedProps = this.driver.joinedProps(meta, this.#state.populate);
1009
+ const stack = [];
1010
+ const hash = (data) => {
1011
+ return Utils.getPrimaryKeyHash(meta.primaryKeys.map(pk => data[pk]));
1476
1012
  };
1477
- nest(prop);
1478
- return;
1479
- }
1480
- if (prop && prop.fieldNames.length > 1 && !prop.fieldNames.includes(f)) {
1481
- if (customAlias) {
1482
- throw new Error(
1483
- `Cannot use 'as ${customAlias}' alias on '${field}' because it expands to multiple columns (${prop.fieldNames.join(', ')}).`,
1484
- );
1485
- }
1486
- ret.push(...prop.fieldNames.map(f => getFieldName(f)));
1487
- return;
1488
- }
1489
- ret.push(getFieldName(field, customAlias));
1490
- });
1491
- const requiresSQLConversion = this.mainAlias.meta.props.filter(
1492
- p => p.hasConvertToJSValueSQL && p.persist !== false,
1493
- );
1494
- if (
1495
- this.#state.flags.has(QueryFlag.CONVERT_CUSTOM_TYPES) &&
1496
- (fields.includes('*') || fields.includes(`${this.mainAlias.aliasName}.*`)) &&
1497
- requiresSQLConversion.length > 0
1498
- ) {
1499
- for (const p of requiresSQLConversion) {
1500
- ret.push(this.helper.mapper(p.name, this.type));
1501
- }
1502
- }
1503
- for (const f of Object.keys(this.#state.populateMap)) {
1504
- if (type === 'where' && this.#state.joins[f]) {
1505
- ret.push(...this.helper.mapJoinColumns(this.type, this.#state.joins[f]));
1506
- }
1507
- }
1508
- return Utils.unique(ret);
1509
- }
1510
- /**
1511
- * Resolves nested paths like `a.books.title` to their actual field references.
1512
- * Auto-joins relations as needed and returns `{alias}.{field}`.
1513
- * For embeddeds: navigates into flattened embeddeds to return the correct field name.
1514
- */
1515
- resolveNestedPath(field) {
1516
- if (typeof field !== 'string' || !field.includes('.')) {
1517
- return field;
1518
- }
1519
- const parts = field.split('.');
1520
- // Simple alias.property case - let prepareFields handle it
1521
- if (parts.length === 2 && this.#state.aliases[parts[0]]) {
1522
- return field;
1523
- }
1524
- // Start with root alias
1525
- let currentAlias = parts[0];
1526
- let currentMeta = this.#state.aliases[currentAlias]
1527
- ? this.metadata.get(this.#state.aliases[currentAlias].entityName)
1528
- : this.mainAlias.meta;
1529
- // If first part is not an alias, it's a property of the main entity
1530
- if (!this.#state.aliases[currentAlias]) {
1531
- currentAlias = this.mainAlias.aliasName;
1532
- parts.unshift(currentAlias);
1533
- }
1534
- // Walk through the path parts (skip the alias)
1535
- for (let i = 1; i < parts.length; i++) {
1536
- const propName = parts[i];
1537
- const prop = currentMeta.properties[propName];
1538
- if (!prop) {
1539
- return field; // Unknown property, return as-is for raw SQL support
1540
- }
1541
- const isLastPart = i === parts.length - 1;
1542
- // Handle embedded properties - navigate into flattened embeddeds
1543
- if (prop.kind === ReferenceKind.EMBEDDED) {
1544
- if (prop.object) {
1545
- return `${currentAlias}.${propName}`;
1546
- }
1547
- // Navigate through remaining path to find the leaf property
1548
- const remainingPath = parts.slice(i + 1);
1549
- let embeddedProp = prop;
1550
- for (const part of remainingPath) {
1551
- embeddedProp = embeddedProp?.embeddedProps?.[part];
1552
- if (embeddedProp?.object && embeddedProp.fieldNames?.[0]) {
1553
- return `${currentAlias}.${embeddedProp.fieldNames[0]}`;
1554
- }
1555
- }
1556
- return `${currentAlias}.${embeddedProp?.fieldNames?.[0] ?? propName}`;
1557
- }
1558
- // Handle relations - auto-join if not the last part
1559
- if (
1560
- prop.kind === ReferenceKind.MANY_TO_ONE ||
1561
- prop.kind === ReferenceKind.ONE_TO_ONE ||
1562
- prop.kind === ReferenceKind.ONE_TO_MANY ||
1563
- prop.kind === ReferenceKind.MANY_TO_MANY
1564
- ) {
1565
- if (isLastPart) {
1566
- return `${currentAlias}.${propName}`;
1567
- }
1568
- // Find existing join or create new one
1569
- const joinPath = parts.slice(0, i + 1).join('.');
1570
- const existingJoinKey = Object.keys(this.#state.joins).find(k => {
1571
- const join = this.#state.joins[k];
1572
- // Check by path or by key prefix (key format is `alias.field#joinAlias`)
1573
- return join.path === joinPath || k.startsWith(`${currentAlias}.${propName}#`);
1013
+ for await (const row of res) {
1014
+ const mapped = this.driver.mapResult(row, meta, this.#state.populate, this);
1015
+ if (!options.mergeResults || joinedProps.length === 0) {
1016
+ yield this.mapResult(mapped, options.mapResults);
1017
+ continue;
1018
+ }
1019
+ if (stack.length > 0 && hash(stack[stack.length - 1]) !== hash(mapped)) {
1020
+ const res = this.driver.mergeJoinedResult(stack, this.mainAlias.meta, joinedProps);
1021
+ for (const row of res) {
1022
+ yield this.mapResult(row, options.mapResults);
1023
+ }
1024
+ stack.length = 0;
1025
+ }
1026
+ stack.push(mapped);
1027
+ }
1028
+ if (stack.length > 0) {
1029
+ const merged = this.driver.mergeJoinedResult(stack, this.mainAlias.meta, joinedProps);
1030
+ yield this.mapResult(merged[0], options.mapResults);
1031
+ }
1032
+ }
1033
+ /**
1034
+ * Alias for `qb.getResultList()`
1035
+ */
1036
+ async getResult() {
1037
+ return this.getResultList();
1038
+ }
1039
+ /**
1040
+ * Executes the query, returning array of results mapped to entity instances.
1041
+ */
1042
+ async getResultList(limit) {
1043
+ await this.em.tryFlush(this.mainAlias.entityName, { flushMode: this.#state.flushMode });
1044
+ const res = await this.execute('all', true);
1045
+ return this.mapResults(res, limit);
1046
+ }
1047
+ propagatePopulateHint(entity, hint) {
1048
+ helper(entity).__serializationContext.populate = hint.concat(helper(entity).__serializationContext.populate ?? []);
1049
+ hint.forEach(hint => {
1050
+ const [propName] = hint.field.split(':', 2);
1051
+ const value = Reference.unwrapReference(entity[propName]);
1052
+ if (Utils.isEntity(value)) {
1053
+ this.propagatePopulateHint(value, hint.children ?? []);
1054
+ }
1055
+ else if (Utils.isCollection(value)) {
1056
+ value.populated();
1057
+ value.getItems(false).forEach(item => this.propagatePopulateHint(item, hint.children ?? []));
1058
+ }
1574
1059
  });
1575
- let joinAlias;
1576
- if (existingJoinKey) {
1577
- joinAlias = this.#state.joins[existingJoinKey].alias;
1578
- } else {
1579
- joinAlias = this.getNextAlias(prop.targetMeta?.className ?? propName);
1580
- this.join(`${currentAlias}.${propName}`, joinAlias, {}, JoinType.leftJoin);
1581
- }
1582
- currentAlias = joinAlias;
1583
- currentMeta = prop.targetMeta;
1584
- continue;
1585
- }
1586
- // Scalar property - return it (if not last part, it's an invalid path but let SQL handle it)
1587
- return `${currentAlias}.${propName}`;
1588
- }
1589
- return field;
1590
- }
1591
- init(type, data, cond) {
1592
- this.ensureNotFinalized();
1593
- this.#state.type = type;
1594
- if ([QueryType.UPDATE, QueryType.DELETE].includes(type) && Utils.hasObjectKeys(this.#state.cond)) {
1595
- throw new Error(
1596
- `You are trying to call \`qb.where().${type.toLowerCase()}()\`. Calling \`qb.${type.toLowerCase()}()\` before \`qb.where()\` is required.`,
1597
- );
1598
- }
1599
- if (!this.helper.isTableNameAliasRequired(type)) {
1600
- this.#state.fields = undefined;
1601
- }
1602
- if (data) {
1603
- if (Utils.isEntity(data)) {
1604
- data = this.em?.getComparator().prepareEntity(data) ?? serialize(data);
1605
- }
1606
- this.#state.data = this.helper.processData(data, this.#state.flags.has(QueryFlag.CONVERT_CUSTOM_TYPES), false);
1607
- }
1608
- if (cond) {
1609
- this.where(cond);
1610
- }
1611
- return this;
1612
- }
1613
- getQueryBase(processVirtualEntity) {
1614
- const qb = this.platform.createNativeQueryBuilder().setFlags(this.#state.flags);
1615
- const { subQuery, aliasName, entityName, meta, rawTableName } = this.mainAlias;
1616
- const requiresAlias =
1617
- this.#state.finalized && (this.#state.explicitAlias || this.helper.isTableNameAliasRequired(this.type));
1618
- const alias = requiresAlias ? aliasName : undefined;
1619
- const schema = this.getSchema(this.mainAlias);
1620
- const tableName = rawTableName
1621
- ? rawTableName
1622
- : subQuery instanceof NativeQueryBuilder
1623
- ? subQuery.as(aliasName)
1624
- : subQuery
1625
- ? raw(`(${subQuery.sql}) as ${this.platform.quoteIdentifier(aliasName)}`, subQuery.params)
1626
- : this.helper.getTableName(entityName);
1627
- const joinSchema = this.#state.schema ?? this.em?.schema ?? schema;
1628
- const schemaOverride = this.#state.schema ?? this.em?.schema;
1629
- if (meta.virtual && processVirtualEntity) {
1630
- qb.from(raw(this.fromVirtual(meta)), { indexHint: this.#state.indexHint });
1631
- } else {
1632
- qb.from(tableName, {
1633
- schema: rawTableName ? undefined : schema,
1634
- alias,
1635
- indexHint: this.#state.indexHint,
1636
- });
1637
- }
1638
- switch (this.type) {
1639
- case QueryType.SELECT:
1640
- qb.select(this.prepareFields(this.#state.fields, 'where', schema));
1641
- if (this.#state.distinctOn) {
1642
- qb.distinctOn(this.prepareFields(this.#state.distinctOn, 'where', schema));
1643
- } else if (this.#state.flags.has(QueryFlag.DISTINCT)) {
1644
- qb.distinct();
1645
- }
1646
- this.helper.processJoins(qb, this.#state.joins, joinSchema, schemaOverride);
1647
- break;
1648
- case QueryType.COUNT: {
1649
- const fields = this.#state.fields.map(f => this.helper.mapper(f, this.type, undefined, undefined, schema));
1650
- qb.count(fields, this.#state.flags.has(QueryFlag.DISTINCT));
1651
- this.helper.processJoins(qb, this.#state.joins, joinSchema, schemaOverride);
1652
- break;
1653
- }
1654
- case QueryType.INSERT:
1655
- qb.insert(this.#state.data);
1656
- break;
1657
- case QueryType.UPDATE:
1658
- qb.update(this.#state.data);
1659
- this.helper.processJoins(qb, this.#state.joins, joinSchema, schemaOverride);
1660
- this.helper.updateVersionProperty(qb, this.#state.data);
1661
- break;
1662
- case QueryType.DELETE:
1663
- qb.delete();
1664
- break;
1665
- case QueryType.TRUNCATE:
1666
- qb.truncate();
1667
- break;
1668
- }
1669
- return qb;
1670
- }
1671
- applyDiscriminatorCondition() {
1672
- const meta = this.mainAlias.meta;
1673
- if (meta.root.inheritanceType !== 'sti' || !meta.discriminatorValue) {
1674
- return;
1675
- }
1676
- const types = Object.values(meta.root.discriminatorMap).map(cls => this.metadata.get(cls));
1677
- const children = [];
1678
- const lookUpChildren = (ret, type) => {
1679
- const children = types.filter(meta2 => meta2.extends === type);
1680
- children.forEach(m => lookUpChildren(ret, m.class));
1681
- ret.push(...children.filter(c => c.discriminatorValue));
1682
- return children;
1683
- };
1684
- lookUpChildren(children, meta.class);
1685
- this.andWhere({
1686
- [meta.root.discriminatorColumn]:
1687
- children.length > 0
1688
- ? { $in: [meta.discriminatorValue, ...children.map(c => c.discriminatorValue)] }
1689
- : meta.discriminatorValue,
1690
- });
1691
- }
1692
- /**
1693
- * Ensures TPT joins are applied. Can be called early before finalize() to populate
1694
- * the _tptAlias map for use in join resolution. Safe to call multiple times.
1695
- * @internal
1696
- */
1697
- ensureTPTJoins() {
1698
- this.applyTPTJoins();
1699
- }
1700
- /**
1701
- * For TPT (Table-Per-Type) inheritance: INNER JOINs parent tables.
1702
- * When querying a child entity, we need to join all parent tables.
1703
- * Field selection is handled separately in addTPTParentFields().
1704
- */
1705
- applyTPTJoins() {
1706
- const meta = this.mainAlias.meta;
1707
- if (
1708
- meta?.inheritanceType !== 'tpt' ||
1709
- !meta.tptParent ||
1710
- ![QueryType.SELECT, QueryType.COUNT].includes(this.type)
1711
- ) {
1712
- return;
1713
- }
1714
- if (this.#state.tptJoinsApplied) {
1715
- return;
1716
- }
1717
- this.#state.tptJoinsApplied = true;
1718
- let childMeta = meta;
1719
- let childAlias = this.mainAlias.aliasName;
1720
- while (childMeta.tptParent) {
1721
- const parentMeta = childMeta.tptParent;
1722
- const parentAlias = this.getNextAlias(parentMeta.className);
1723
- this.createAlias(parentMeta.class, parentAlias);
1724
- this.#state.tptAlias[parentMeta.className] = parentAlias;
1725
- this.addPropertyJoin(
1726
- childMeta.tptParentProp,
1727
- childAlias,
1728
- parentAlias,
1729
- JoinType.innerJoin,
1730
- `[tpt]${childMeta.className}`,
1731
- );
1732
- childMeta = parentMeta;
1733
- childAlias = parentAlias;
1734
- }
1735
- }
1736
- /**
1737
- * For TPT inheritance: adds field selections from parent tables.
1738
- */
1739
- addTPTParentFields() {
1740
- const meta = this.mainAlias.meta;
1741
- if (
1742
- meta?.inheritanceType !== 'tpt' ||
1743
- !meta.tptParent ||
1744
- ![QueryType.SELECT, QueryType.COUNT].includes(this.type)
1745
- ) {
1746
- return;
1747
- }
1748
- if (!this.#state.fields?.includes('*') && !this.#state.fields?.includes(`${this.mainAlias.aliasName}.*`)) {
1749
- return;
1750
- }
1751
- let parentMeta = meta.tptParent;
1752
- while (parentMeta) {
1753
- const parentAlias = this.#state.tptAlias[parentMeta.className];
1754
- if (parentAlias) {
1755
- const schema = parentMeta.schema === '*' ? '*' : this.driver.getSchemaName(parentMeta);
1756
- parentMeta.ownProps
1757
- .filter(prop => this.platform.shouldHaveColumn(prop, []))
1758
- .forEach(prop =>
1759
- this.#state.fields.push(...this.driver.mapPropToFieldNames(this, prop, parentAlias, parentMeta, schema)),
1760
- );
1761
- }
1762
- parentMeta = parentMeta.tptParent;
1763
- }
1764
- }
1765
- /**
1766
- * For TPT polymorphic queries: LEFT JOINs all child tables when querying a TPT base class.
1767
- * Adds discriminator and child fields to determine and load the concrete type.
1768
- */
1769
- applyTPTPolymorphicJoins() {
1770
- const meta = this.mainAlias.meta;
1771
- const descendants = meta?.allTPTDescendants;
1772
- if (!descendants?.length || ![QueryType.SELECT, QueryType.COUNT].includes(this.type)) {
1773
- return;
1774
- }
1775
- if (!this.#state.fields?.includes('*') && !this.#state.fields?.includes(`${this.mainAlias.aliasName}.*`)) {
1776
- return;
1777
- }
1778
- // LEFT JOIN each descendant table and add their fields
1779
- for (const childMeta of descendants) {
1780
- const childAlias = this.getNextAlias(childMeta.className);
1781
- this.createAlias(childMeta.class, childAlias);
1782
- this.#state.tptAlias[childMeta.className] = childAlias;
1783
- this.addPropertyJoin(
1784
- childMeta.tptInverseProp,
1785
- this.mainAlias.aliasName,
1786
- childAlias,
1787
- JoinType.leftJoin,
1788
- `[tpt]${meta.className}`,
1789
- );
1790
- // Add child fields
1791
- const schema = childMeta.schema === '*' ? '*' : this.driver.getSchemaName(childMeta);
1792
- childMeta.ownProps
1793
- .filter(prop => !prop.primary && this.platform.shouldHaveColumn(prop, []))
1794
- .forEach(prop =>
1795
- this.#state.fields.push(...this.driver.mapPropToFieldNames(this, prop, childAlias, childMeta, schema)),
1796
- );
1797
- }
1798
- // Add computed discriminator (CASE WHEN to determine concrete type)
1799
- // descendants is pre-sorted by depth (deepest first) during discovery
1800
- if (meta.tptDiscriminatorColumn) {
1801
- this.#state.fields.push(
1802
- this.driver.buildTPTDiscriminatorExpression(meta, descendants, this.#state.tptAlias, this.mainAlias.aliasName),
1803
- );
1804
- }
1805
- }
1806
- finalize() {
1807
- if (this.#state.finalized) {
1808
- return;
1809
- }
1810
- if (!this.#state.type) {
1811
- this.select('*');
1812
- }
1813
- const meta = this.mainAlias.meta;
1814
- this.applyDiscriminatorCondition();
1815
- this.applyTPTJoins();
1816
- this.addTPTParentFields();
1817
- this.applyTPTPolymorphicJoins();
1818
- this.processPopulateHint();
1819
- this.processNestedJoins();
1820
- if (meta && (this.#state.fields?.includes('*') || this.#state.fields?.includes(`${this.mainAlias.aliasName}.*`))) {
1821
- const schema = this.getSchema(this.mainAlias);
1822
- // Create a column mapping with unquoted aliases - quoting should be handled by the user via `quote` helper
1823
- // For TPT, use helper to resolve correct alias per property (inherited props use parent alias)
1824
- const quotedMainAlias = this.platform.quoteIdentifier(this.mainAlias.aliasName).toString();
1825
- const columns = meta.createColumnMappingObject(
1826
- prop => this.helper.getTPTAliasForProperty(prop.name, this.mainAlias.aliasName),
1827
- quotedMainAlias,
1828
- );
1829
- meta.props
1830
- .filter(prop => prop.formula && (!prop.lazy || this.#state.flags.has(QueryFlag.INCLUDE_LAZY_FORMULAS)))
1831
- .map(prop => {
1832
- const aliased = this.platform.quoteIdentifier(prop.fieldNames[0]);
1833
- const table = this.helper.createFormulaTable(quotedMainAlias, meta, schema);
1834
- return `${this.driver.evaluateFormula(prop.formula, columns, table)} as ${aliased}`;
1835
- })
1836
- .filter(
1837
- field =>
1838
- !this.#state.fields.some(f => {
1839
- if (isRaw(f)) {
1840
- return f.sql === field && f.params.length === 0;
1841
- }
1842
- return f === field;
1843
- }),
1844
- )
1845
- .forEach(field => this.#state.fields.push(raw(field)));
1846
- }
1847
- QueryHelper.processObjectParams(this.#state.data);
1848
- QueryHelper.processObjectParams(this.#state.cond);
1849
- QueryHelper.processObjectParams(this.#state.having);
1850
- // automatically enable paginate flag when we detect to-many joins, but only if there is no `group by` clause
1851
- if (
1852
- !this.#state.flags.has(QueryFlag.DISABLE_PAGINATE) &&
1853
- this.#state.groupBy.length === 0 &&
1854
- this.hasToManyJoins()
1855
- ) {
1856
- this.#state.flags.add(QueryFlag.PAGINATE);
1857
- }
1858
- if (
1859
- meta &&
1860
- !meta.virtual &&
1861
- this.#state.flags.has(QueryFlag.PAGINATE) &&
1862
- !this.#state.flags.has(QueryFlag.DISABLE_PAGINATE) &&
1863
- (this.#state.limit > 0 || this.#state.offset > 0)
1864
- ) {
1865
- this.wrapPaginateSubQuery(meta);
1866
- }
1867
- if (
1868
- meta &&
1869
- (this.#state.flags.has(QueryFlag.UPDATE_SUB_QUERY) || this.#state.flags.has(QueryFlag.DELETE_SUB_QUERY))
1870
- ) {
1871
- this.wrapModifySubQuery(meta);
1872
- }
1873
- this.#state.finalized = true;
1874
- }
1875
- /** @internal */
1876
- processPopulateHint() {
1877
- if (this.#state.populateHintFinalized) {
1878
- return;
1879
- }
1880
- const meta = this.mainAlias.meta;
1881
- if (meta && this.#state.flags.has(QueryFlag.AUTO_JOIN_ONE_TO_ONE_OWNER)) {
1882
- const relationsToPopulate = this.#state.populate.map(({ field }) => field);
1883
- meta.relations
1884
- .filter(
1885
- prop =>
1886
- prop.kind === ReferenceKind.ONE_TO_ONE &&
1887
- !prop.owner &&
1888
- !relationsToPopulate.includes(prop.name) &&
1889
- !relationsToPopulate.includes(`${prop.name}:ref`),
1890
- )
1891
- .map(prop => ({ field: `${prop.name}:ref` }))
1892
- .forEach(item => this.#state.populate.push(item));
1893
- }
1894
- this.#state.populate.forEach(({ field }) => {
1895
- const [fromAlias, fromField] = this.helper.splitField(field);
1896
- const aliasedField = `${fromAlias}.${fromField}`;
1897
- const join = Object.keys(this.#state.joins).find(k => `${aliasedField}#${this.#state.joins[k].alias}` === k);
1898
- if (join && this.#state.joins[join] && this.helper.isOneToOneInverse(fromField)) {
1899
- this.#state.populateMap[join] = this.#state.joins[join].alias;
1900
- return;
1901
- }
1902
- if (meta && this.helper.isOneToOneInverse(fromField)) {
1060
+ }
1061
+ mapResult(row, map = true) {
1062
+ if (!map) {
1063
+ return row;
1064
+ }
1065
+ const entity = this.em.map(this.mainAlias.entityName, row, { schema: this.#state.schema });
1066
+ this.propagatePopulateHint(entity, this.#state.populate);
1067
+ return entity;
1068
+ }
1069
+ mapResults(res, limit) {
1070
+ const entities = [];
1071
+ for (const row of res) {
1072
+ const entity = this.mapResult(row);
1073
+ this.propagatePopulateHint(entity, this.#state.populate);
1074
+ entities.push(entity);
1075
+ if (limit != null && --limit === 0) {
1076
+ break;
1077
+ }
1078
+ }
1079
+ return Utils.unique(entities);
1080
+ }
1081
+ /**
1082
+ * Executes the query, returning the first result or null
1083
+ */
1084
+ async getSingleResult() {
1085
+ if (!this.#state.finalized) {
1086
+ this.limit(1);
1087
+ }
1088
+ const [res] = await this.getResultList(1);
1089
+ return res || null;
1090
+ }
1091
+ async getCount(field, distinct) {
1092
+ let res;
1093
+ if (this.type === QueryType.COUNT) {
1094
+ res = await this.execute('get', false);
1095
+ }
1096
+ else {
1097
+ const qb = (this.#state.type === undefined ? this : this.clone());
1098
+ qb.processPopulateHint(); // needs to happen sooner so `qb.hasToManyJoins()` reports correctly
1099
+ qb.count(field, distinct ?? qb.hasToManyJoins())
1100
+ .limit(undefined)
1101
+ .offset(undefined)
1102
+ .orderBy([]);
1103
+ res = await qb.execute('get', false);
1104
+ }
1105
+ return res ? +res.count : 0;
1106
+ }
1107
+ /**
1108
+ * Executes the query, returning both array of results and total count query (without offset and limit).
1109
+ */
1110
+ async getResultAndCount() {
1111
+ return [await this.clone().getResultList(), await this.clone().getCount()];
1112
+ }
1113
+ as(aliasOrTargetEntity, alias) {
1114
+ const qb = this.getNativeQuery();
1115
+ let finalAlias = aliasOrTargetEntity;
1116
+ /* v8 ignore next */
1117
+ if (typeof aliasOrTargetEntity === 'string' && aliasOrTargetEntity.includes('.')) {
1118
+ throw new Error('qb.as(alias) no longer supports target entity name prefix, use qb.as(TargetEntity, key) signature instead');
1119
+ }
1120
+ if (alias) {
1121
+ const meta = this.metadata.get(aliasOrTargetEntity);
1122
+ /* v8 ignore next */
1123
+ finalAlias = meta.properties[alias]?.fieldNames[0] ?? alias;
1124
+ }
1125
+ qb.as(finalAlias);
1126
+ // tag the instance, so it is possible to detect it easily
1127
+ Object.defineProperty(qb, '__as', { enumerable: false, value: finalAlias });
1128
+ return qb;
1129
+ }
1130
+ /**
1131
+ * Combines the current query with one or more other queries using `UNION ALL`.
1132
+ * All queries must select the same columns. Returns a `QueryBuilder` that
1133
+ * can be used with `$in`, passed to `qb.from()`, or converted via `.getQuery()`,
1134
+ * `.getParams()`, `.toQuery()`, `.toRaw()`, etc.
1135
+ *
1136
+ * ```ts
1137
+ * const qb1 = em.createQueryBuilder(Employee).select('id').where(condition1);
1138
+ * const qb2 = em.createQueryBuilder(Employee).select('id').where(condition2);
1139
+ * const qb3 = em.createQueryBuilder(Employee).select('id').where(condition3);
1140
+ * const subquery = qb1.unionAll(qb2, qb3);
1141
+ *
1142
+ * const results = await em.find(Employee, { id: { $in: subquery } });
1143
+ * ```
1144
+ */
1145
+ unionAll(...others) {
1146
+ return this.buildUnionQuery('union all', others);
1147
+ }
1148
+ /**
1149
+ * Combines the current query with one or more other queries using `UNION` (with deduplication).
1150
+ * All queries must select the same columns. Returns a `QueryBuilder` that
1151
+ * can be used with `$in`, passed to `qb.from()`, or converted via `.getQuery()`,
1152
+ * `.getParams()`, `.toQuery()`, `.toRaw()`, etc.
1153
+ *
1154
+ * ```ts
1155
+ * const qb1 = em.createQueryBuilder(Employee).select('id').where(condition1);
1156
+ * const qb2 = em.createQueryBuilder(Employee).select('id').where(condition2);
1157
+ * const subquery = qb1.union(qb2);
1158
+ *
1159
+ * const results = await em.find(Employee, { id: { $in: subquery } });
1160
+ * ```
1161
+ */
1162
+ union(...others) {
1163
+ return this.buildUnionQuery('union', others);
1164
+ }
1165
+ buildUnionQuery(separator, others) {
1166
+ const all = [this, ...others];
1167
+ const parts = [];
1168
+ const params = [];
1169
+ for (const qb of all) {
1170
+ const compiled = qb instanceof _a ? qb.toQuery() : qb.compile();
1171
+ parts.push(`(${compiled.sql})`);
1172
+ params.push(...compiled.params);
1173
+ }
1174
+ const result = this.clone(true);
1175
+ result.#state.unionQuery = { sql: parts.join(` ${separator} `), params };
1176
+ return result;
1177
+ }
1178
+ with(name, query, options) {
1179
+ return this.addCte(name, query, options);
1180
+ }
1181
+ withRecursive(name, query, options) {
1182
+ return this.addCte(name, query, options, true);
1183
+ }
1184
+ addCte(name, query, options, recursive) {
1185
+ this.ensureNotFinalized();
1186
+ if (this.#state.ctes.some(cte => cte.name === name)) {
1187
+ throw new Error(`CTE with name '${name}' already exists`);
1188
+ }
1189
+ // Eagerly compile QueryBuilder to RawQueryFragment — later mutations to the sub-query won't be reflected
1190
+ const compiled = query instanceof _a ? query.toRaw() : query;
1191
+ this.#state.ctes.push({
1192
+ name,
1193
+ query: compiled,
1194
+ recursive,
1195
+ columns: options?.columns,
1196
+ materialized: options?.materialized,
1197
+ });
1198
+ return this;
1199
+ }
1200
+ clone(reset, preserve) {
1201
+ const qb = new _a(this.#state.mainAlias.entityName, this.metadata, this.driver, this.context, this.#state.mainAlias.aliasName, this.connectionType, this.em);
1202
+ if (reset !== true) {
1203
+ qb.#state = Utils.copy(this.#state);
1204
+ // CTEs contain NativeQueryBuilder instances that should not be deep-cloned
1205
+ qb.#state.ctes = this.#state.ctes.map(cte => ({ ...cte }));
1206
+ if (Array.isArray(reset)) {
1207
+ const fresh = _a.createDefaultState();
1208
+ for (const key of reset) {
1209
+ qb.#state[key] = fresh[key];
1210
+ }
1211
+ }
1212
+ }
1213
+ else if (preserve) {
1214
+ for (const key of preserve) {
1215
+ qb.#state[key] = Utils.copy(this.#state[key]);
1216
+ }
1217
+ }
1218
+ qb.#state.finalized = false;
1219
+ qb.#query = undefined;
1220
+ qb.#helper = qb.createQueryBuilderHelper();
1221
+ return qb;
1222
+ }
1223
+ /**
1224
+ * Sets logger context for this query builder.
1225
+ */
1226
+ setLoggerContext(context) {
1227
+ this.loggerContext = context;
1228
+ }
1229
+ /**
1230
+ * Gets logger context for this query builder.
1231
+ */
1232
+ getLoggerContext() {
1233
+ this.loggerContext ??= {};
1234
+ return this.loggerContext;
1235
+ }
1236
+ fromVirtual(meta) {
1237
+ if (typeof meta.expression === 'string') {
1238
+ return `(${meta.expression}) as ${this.platform.quoteIdentifier(this.alias)}`;
1239
+ }
1240
+ const res = meta.expression(this.em, this.#state.cond, {});
1241
+ if (typeof res === 'string') {
1242
+ return `(${res}) as ${this.platform.quoteIdentifier(this.alias)}`;
1243
+ }
1244
+ if (res instanceof _a) {
1245
+ return `(${res.getFormattedQuery()}) as ${this.platform.quoteIdentifier(this.alias)}`;
1246
+ }
1247
+ if (isRaw(res)) {
1248
+ const query = this.platform.formatQuery(res.sql, res.params);
1249
+ return `(${query}) as ${this.platform.quoteIdentifier(this.alias)}`;
1250
+ }
1251
+ /* v8 ignore next */
1252
+ return res;
1253
+ }
1254
+ /**
1255
+ * Adds a join from a property object. Used internally for TPT joins where the property
1256
+ * is synthetic (not in entity.properties) but defined on metadata (e.g., tptParentProp).
1257
+ * The caller must create the alias first via createAlias().
1258
+ * @internal
1259
+ */
1260
+ addPropertyJoin(prop, ownerAlias, alias, type, path, schema) {
1261
+ schema ??= prop.targetMeta?.schema === '*' ? '*' : this.driver.getSchemaName(prop.targetMeta);
1262
+ const key = `[tpt]${ownerAlias}#${alias}`;
1263
+ this.#state.joins[key] =
1264
+ prop.kind === ReferenceKind.MANY_TO_ONE
1265
+ ? this.helper.joinManyToOneReference(prop, ownerAlias, alias, type, {}, schema)
1266
+ : this.helper.joinOneToReference(prop, ownerAlias, alias, type, {}, schema);
1267
+ this.#state.joins[key].path = path;
1268
+ return key;
1269
+ }
1270
+ joinReference(field, alias, cond, type, path, schema, subquery) {
1271
+ this.ensureNotFinalized();
1272
+ if (typeof field === 'object') {
1273
+ const prop = {
1274
+ name: '__subquery__',
1275
+ kind: ReferenceKind.MANY_TO_ONE,
1276
+ };
1277
+ if (field instanceof _a) {
1278
+ prop.type = Utils.className(field.mainAlias.entityName);
1279
+ prop.targetMeta = field.mainAlias.meta;
1280
+ field = field.getNativeQuery();
1281
+ }
1282
+ if (isRaw(field)) {
1283
+ field = this.platform.formatQuery(field.sql, field.params);
1284
+ }
1285
+ const key = `${this.alias}.${prop.name}#${alias}`;
1286
+ this.#state.joins[key] = {
1287
+ prop,
1288
+ alias,
1289
+ type,
1290
+ cond,
1291
+ schema,
1292
+ subquery: field.toString(),
1293
+ ownerAlias: this.alias,
1294
+ };
1295
+ return { prop, key };
1296
+ }
1297
+ if (!subquery && type.includes('lateral')) {
1298
+ throw new Error(`Lateral join can be used only with a sub-query.`);
1299
+ }
1300
+ const [fromAlias, fromField] = this.helper.splitField(field);
1301
+ const q = (str) => `'${str}'`;
1302
+ if (!this.#state.aliases[fromAlias]) {
1303
+ throw new Error(`Trying to join ${q(fromField)} with alias ${q(fromAlias)}, but ${q(fromAlias)} is not a known alias. Available aliases are: ${Object.keys(this.#state.aliases).map(q).join(', ')}.`);
1304
+ }
1305
+ const entityName = this.#state.aliases[fromAlias].entityName;
1306
+ const meta = this.metadata.get(entityName);
1903
1307
  const prop = meta.properties[fromField];
1904
- const alias = this.getNextAlias(prop.pivotEntity ?? prop.targetMeta.class);
1905
- const aliasedName = `${fromAlias}.${prop.name}#${alias}`;
1906
- this.#state.joins[aliasedName] = this.helper.joinOneToReference(
1907
- prop,
1908
- this.mainAlias.aliasName,
1909
- alias,
1910
- JoinType.leftJoin,
1911
- );
1912
- this.#state.joins[aliasedName].path =
1913
- `${Object.values(this.#state.joins).find(j => j.alias === fromAlias)?.path ?? meta.className}.${prop.name}`;
1914
- this.#state.populateMap[aliasedName] = this.#state.joins[aliasedName].alias;
1308
+ if (!prop) {
1309
+ throw new Error(`Trying to join ${q(field)}, but ${q(fromField)} is not a defined relation on ${meta.className}.`);
1310
+ }
1311
+ // For TPT inheritance, owning relations (M:1 and owning 1:1) may have FK columns in a parent table
1312
+ // Resolve the correct alias for the table that owns the FK column
1313
+ const ownerAlias = prop.kind === ReferenceKind.MANY_TO_ONE || (prop.kind === ReferenceKind.ONE_TO_ONE && prop.owner)
1314
+ ? this.helper.getTPTAliasForProperty(fromField, fromAlias)
1315
+ : fromAlias;
1915
1316
  this.createAlias(prop.targetMeta.class, alias);
1916
- }
1917
- });
1918
- this.processPopulateWhere(false);
1919
- this.processPopulateWhere(true);
1920
- this.#state.populateHintFinalized = true;
1921
- }
1922
- processPopulateWhere(filter) {
1923
- const value = filter ? this.#state.populateFilter : this.#state.populateWhere;
1924
- if (value == null || value === PopulateHint.ALL) {
1925
- return;
1926
- }
1927
- let joins = Object.values(this.#state.joins);
1928
- for (const join of joins) {
1929
- join.cond_ ??= join.cond;
1930
- join.cond = { ...join.cond };
1931
- }
1932
- if (typeof value === 'object') {
1933
- const cond = CriteriaNodeFactory.createNode(this.metadata, this.mainAlias.entityName, value).process(this, {
1934
- matchPopulateJoins: true,
1935
- ignoreBranching: true,
1936
- preferNoBranch: true,
1937
- filter,
1938
- });
1939
- // there might be new joins created by processing the `populateWhere` object
1940
- joins = Object.values(this.#state.joins);
1941
- this.mergeOnConditions(joins, cond, filter);
1942
- }
1943
- }
1944
- mergeOnConditions(joins, cond, filter, op) {
1945
- for (const k of Object.keys(cond)) {
1946
- if (Utils.isOperator(k)) {
1947
- if (Array.isArray(cond[k])) {
1948
- cond[k].forEach(c => this.mergeOnConditions(joins, c, filter, k));
1317
+ cond = QueryHelper.processWhere({
1318
+ where: cond,
1319
+ entityName: this.mainAlias.entityName,
1320
+ metadata: this.metadata,
1321
+ platform: this.platform,
1322
+ aliasMap: this.getAliasMap(),
1323
+ aliased: [QueryType.SELECT, QueryType.COUNT].includes(this.type),
1324
+ });
1325
+ const criteriaNode = CriteriaNodeFactory.createNode(this.metadata, prop.targetMeta.class, cond);
1326
+ cond = criteriaNode.process(this, { ignoreBranching: true, alias });
1327
+ let aliasedName = `${fromAlias}.${prop.name}#${alias}`;
1328
+ path ??= `${Object.values(this.#state.joins).find(j => j.alias === fromAlias)?.path ?? Utils.className(entityName)}.${prop.name}`;
1329
+ if (prop.kind === ReferenceKind.ONE_TO_MANY) {
1330
+ this.#state.joins[aliasedName] = this.helper.joinOneToReference(prop, fromAlias, alias, type, cond, schema);
1331
+ this.#state.joins[aliasedName].path ??= path;
1949
1332
  }
1950
- /* v8 ignore next */
1951
- this.mergeOnConditions(joins, cond[k], filter, k);
1952
- }
1953
- const [alias] = this.helper.splitField(k);
1954
- const join = joins.find(j => j.alias === alias);
1955
- if (join) {
1956
- const parentJoin = joins.find(j => j.alias === join.ownerAlias);
1957
- // https://stackoverflow.com/a/56815807/3665878
1958
- if (parentJoin && !filter) {
1959
- const nested = (parentJoin.nested ??= new Set());
1960
- join.type =
1961
- join.type === JoinType.innerJoin ||
1962
- [ReferenceKind.ONE_TO_MANY, ReferenceKind.MANY_TO_MANY].includes(parentJoin.prop.kind)
1963
- ? JoinType.nestedInnerJoin
1964
- : JoinType.nestedLeftJoin;
1965
- nested.add(join);
1966
- }
1967
- if (join.cond[k]) {
1968
- /* v8 ignore next */
1969
- join.cond = { [op ?? '$and']: [join.cond, { [k]: cond[k] }] };
1970
- } else if (op === '$or') {
1971
- join.cond.$or ??= [];
1972
- join.cond.$or.push({ [k]: cond[k] });
1973
- } else {
1974
- join.cond = { ...join.cond, [k]: cond[k] };
1975
- }
1976
- }
1977
- }
1978
- }
1979
- /**
1980
- * When adding an inner join on a left joined relation, we need to nest them,
1981
- * otherwise the inner join could discard rows of the root table.
1982
- */
1983
- processNestedJoins() {
1984
- if (this.#state.flags.has(QueryFlag.DISABLE_NESTED_INNER_JOIN)) {
1985
- return;
1986
- }
1987
- const joins = Object.values(this.#state.joins);
1988
- const lookupParentGroup = j => {
1989
- return j.nested ?? (j.parent ? lookupParentGroup(j.parent) : undefined);
1990
- };
1991
- for (const join of joins) {
1992
- if (join.type === JoinType.innerJoin) {
1993
- join.parent = joins.find(j => j.alias === join.ownerAlias);
1994
- // https://stackoverflow.com/a/56815807/3665878
1995
- if (join.parent?.type === JoinType.leftJoin || join.parent?.type === JoinType.nestedLeftJoin) {
1996
- const nested = (join.parent.nested ??= new Set());
1997
- join.type = join.type === JoinType.innerJoin ? JoinType.nestedInnerJoin : JoinType.nestedLeftJoin;
1998
- nested.add(join);
1999
- } else if (join.parent?.type === JoinType.nestedInnerJoin) {
2000
- const group = lookupParentGroup(join.parent);
2001
- const nested = group ?? (join.parent.nested ??= new Set());
2002
- join.type = join.type === JoinType.innerJoin ? JoinType.nestedInnerJoin : JoinType.nestedLeftJoin;
2003
- nested.add(join);
2004
- }
2005
- }
2006
- }
2007
- }
2008
- hasToManyJoins() {
2009
- return Object.values(this.#state.joins).some(join => {
2010
- return [ReferenceKind.ONE_TO_MANY, ReferenceKind.MANY_TO_MANY].includes(join.prop.kind);
2011
- });
2012
- }
2013
- wrapPaginateSubQuery(meta) {
2014
- const schema = this.getSchema(this.mainAlias);
2015
- const pks = this.prepareFields(meta.primaryKeys, 'sub-query', schema);
2016
- const subQuery = this.clone(['orderBy', 'fields', 'lockMode', 'lockTables'])
2017
- .select(pks)
2018
- .groupBy(pks)
2019
- .limit(this.#state.limit);
2020
- // revert the on conditions added via populateWhere, we want to apply those only once
2021
- for (const join of Object.values(subQuery.#state.joins)) {
2022
- if (join.cond_) {
2023
- join.cond = join.cond_;
2024
- }
2025
- }
2026
- if (this.#state.offset) {
2027
- subQuery.offset(this.#state.offset);
2028
- }
2029
- const addToSelect = [];
2030
- if (this.#state.orderBy.length > 0) {
2031
- const orderBy = [];
2032
- for (const orderMap of this.#state.orderBy) {
2033
- for (const field of Utils.getObjectQueryKeys(orderMap)) {
2034
- const direction = orderMap[field];
2035
- if (RawQueryFragment.isKnownFragmentSymbol(field)) {
2036
- orderBy.push({ [field]: direction });
2037
- continue;
2038
- }
2039
- const [a, f] = this.helper.splitField(field);
2040
- const prop = this.helper.getProperty(f, a);
2041
- const type = this.platform.castColumn(prop);
2042
- const fieldName = this.helper.mapper(field, this.type, undefined, null);
2043
- if (!prop?.persist && !prop?.formula && !prop?.hasConvertToJSValueSQL && !pks.includes(fieldName)) {
2044
- addToSelect.push(fieldName);
2045
- }
2046
- const quoted = this.platform.quoteIdentifier(fieldName);
2047
- const key = raw(`min(${quoted}${type})`);
2048
- orderBy.push({ [key]: direction });
2049
- }
2050
- }
2051
- subQuery.orderBy(orderBy);
2052
- }
2053
- subQuery.#state.finalized = true;
2054
- const innerQuery = subQuery.as(this.mainAlias.aliasName).clear('select').select(pks);
2055
- if (addToSelect.length > 0) {
2056
- addToSelect.forEach(prop => {
2057
- const field = this.#state.fields.find(field => {
2058
- if (typeof field === 'object' && field && '__as' in field) {
2059
- return field.__as === prop;
2060
- }
2061
- if (isRaw(field)) {
2062
- // not perfect, but should work most of the time, ideally we should check only the alias (`... as alias`)
2063
- return field.sql.includes(prop);
2064
- }
2065
- return false;
1333
+ else if (prop.kind === ReferenceKind.MANY_TO_MANY) {
1334
+ let pivotAlias = alias;
1335
+ if (type !== JoinType.pivotJoin) {
1336
+ const oldPivotAlias = this.getAliasForJoinPath(path + '[pivot]');
1337
+ pivotAlias = oldPivotAlias ?? this.getNextAlias(prop.pivotEntity);
1338
+ aliasedName = `${fromAlias}.${prop.name}#${pivotAlias}`;
1339
+ }
1340
+ const joins = this.helper.joinManyToManyReference(prop, fromAlias, alias, pivotAlias, type, cond, path, schema);
1341
+ Object.assign(this.#state.joins, joins);
1342
+ this.createAlias(prop.pivotEntity, pivotAlias);
1343
+ this.#state.joins[aliasedName].path ??= path;
1344
+ aliasedName = Object.keys(joins)[1];
1345
+ }
1346
+ else if (prop.kind === ReferenceKind.ONE_TO_ONE) {
1347
+ this.#state.joins[aliasedName] = this.helper.joinOneToReference(prop, ownerAlias, alias, type, cond, schema);
1348
+ this.#state.joins[aliasedName].path ??= path;
1349
+ }
1350
+ else {
1351
+ // MANY_TO_ONE
1352
+ this.#state.joins[aliasedName] = this.helper.joinManyToOneReference(prop, ownerAlias, alias, type, cond, schema);
1353
+ this.#state.joins[aliasedName].path ??= path;
1354
+ }
1355
+ return { prop, key: aliasedName };
1356
+ }
1357
+ prepareFields(fields, type = 'where', schema) {
1358
+ const ret = [];
1359
+ const getFieldName = (name, customAlias) => {
1360
+ const alias = customAlias ?? (type === 'groupBy' ? null : undefined);
1361
+ return this.helper.mapper(name, this.type, undefined, alias, schema);
1362
+ };
1363
+ fields.forEach(originalField => {
1364
+ if (typeof originalField !== 'string') {
1365
+ ret.push(originalField);
1366
+ return;
1367
+ }
1368
+ // Strip 'as alias' suffix if present — the alias is passed to mapper at the end
1369
+ let field = originalField;
1370
+ let customAlias;
1371
+ const asMatch = FIELD_ALIAS_RE.exec(originalField);
1372
+ if (asMatch) {
1373
+ field = asMatch[1].trim();
1374
+ customAlias = asMatch[2];
1375
+ }
1376
+ const join = Object.keys(this.#state.joins).find(k => field === k.substring(0, k.indexOf('#')));
1377
+ if (join && type === 'where') {
1378
+ ret.push(...this.helper.mapJoinColumns(this.type, this.#state.joins[join]));
1379
+ return;
1380
+ }
1381
+ const [a, f] = this.helper.splitField(field);
1382
+ const prop = this.helper.getProperty(f, a);
1383
+ /* v8 ignore next */
1384
+ if (prop && [ReferenceKind.ONE_TO_MANY, ReferenceKind.MANY_TO_MANY].includes(prop.kind)) {
1385
+ return;
1386
+ }
1387
+ if (prop?.persist === false && !prop.embedded && !prop.formula && type === 'where') {
1388
+ return;
1389
+ }
1390
+ if (prop?.embedded || (prop?.kind === ReferenceKind.EMBEDDED && prop.object)) {
1391
+ const name = prop.embeddedPath?.join('.') ?? prop.fieldNames[0];
1392
+ const aliased = this.#state.aliases[a] ? `${a}.${name}` : name;
1393
+ ret.push(getFieldName(aliased, customAlias));
1394
+ return;
1395
+ }
1396
+ if (prop?.kind === ReferenceKind.EMBEDDED) {
1397
+ if (customAlias) {
1398
+ throw new Error(`Cannot use 'as ${customAlias}' alias on embedded property '${field}' because it expands to multiple columns. Alias individual fields instead (e.g. '${field}.propertyName as ${customAlias}').`);
1399
+ }
1400
+ const nest = (prop) => {
1401
+ for (const childProp of Object.values(prop.embeddedProps)) {
1402
+ if (childProp.fieldNames &&
1403
+ (childProp.kind !== ReferenceKind.EMBEDDED || childProp.object) &&
1404
+ childProp.persist !== false) {
1405
+ ret.push(getFieldName(childProp.fieldNames[0]));
1406
+ }
1407
+ else {
1408
+ nest(childProp);
1409
+ }
1410
+ }
1411
+ };
1412
+ nest(prop);
1413
+ return;
1414
+ }
1415
+ if (prop && prop.fieldNames.length > 1 && !prop.fieldNames.includes(f)) {
1416
+ if (customAlias) {
1417
+ throw new Error(`Cannot use 'as ${customAlias}' alias on '${field}' because it expands to multiple columns (${prop.fieldNames.join(', ')}).`);
1418
+ }
1419
+ ret.push(...prop.fieldNames.map(f => getFieldName(f)));
1420
+ return;
1421
+ }
1422
+ ret.push(getFieldName(field, customAlias));
1423
+ });
1424
+ const requiresSQLConversion = this.mainAlias.meta.props.filter(p => p.hasConvertToJSValueSQL && p.persist !== false);
1425
+ if (this.#state.flags.has(QueryFlag.CONVERT_CUSTOM_TYPES) &&
1426
+ (fields.includes('*') || fields.includes(`${this.mainAlias.aliasName}.*`)) &&
1427
+ requiresSQLConversion.length > 0) {
1428
+ for (const p of requiresSQLConversion) {
1429
+ ret.push(this.helper.mapper(p.name, this.type));
1430
+ }
1431
+ }
1432
+ for (const f of Object.keys(this.#state.populateMap)) {
1433
+ if (type === 'where' && this.#state.joins[f]) {
1434
+ ret.push(...this.helper.mapJoinColumns(this.type, this.#state.joins[f]));
1435
+ }
1436
+ }
1437
+ return Utils.unique(ret);
1438
+ }
1439
+ /**
1440
+ * Resolves nested paths like `a.books.title` to their actual field references.
1441
+ * Auto-joins relations as needed and returns `{alias}.{field}`.
1442
+ * For embeddeds: navigates into flattened embeddeds to return the correct field name.
1443
+ */
1444
+ resolveNestedPath(field) {
1445
+ if (typeof field !== 'string' || !field.includes('.')) {
1446
+ return field;
1447
+ }
1448
+ const parts = field.split('.');
1449
+ // Simple alias.property case - let prepareFields handle it
1450
+ if (parts.length === 2 && this.#state.aliases[parts[0]]) {
1451
+ return field;
1452
+ }
1453
+ // Start with root alias
1454
+ let currentAlias = parts[0];
1455
+ let currentMeta = this.#state.aliases[currentAlias]
1456
+ ? this.metadata.get(this.#state.aliases[currentAlias].entityName)
1457
+ : this.mainAlias.meta;
1458
+ // If first part is not an alias, it's a property of the main entity
1459
+ if (!this.#state.aliases[currentAlias]) {
1460
+ currentAlias = this.mainAlias.aliasName;
1461
+ parts.unshift(currentAlias);
1462
+ }
1463
+ // Walk through the path parts (skip the alias)
1464
+ for (let i = 1; i < parts.length; i++) {
1465
+ const propName = parts[i];
1466
+ const prop = currentMeta.properties[propName];
1467
+ if (!prop) {
1468
+ return field; // Unknown property, return as-is for raw SQL support
1469
+ }
1470
+ const isLastPart = i === parts.length - 1;
1471
+ // Handle embedded properties - navigate into flattened embeddeds
1472
+ if (prop.kind === ReferenceKind.EMBEDDED) {
1473
+ if (prop.object) {
1474
+ return `${currentAlias}.${propName}`;
1475
+ }
1476
+ // Navigate through remaining path to find the leaf property
1477
+ const remainingPath = parts.slice(i + 1);
1478
+ let embeddedProp = prop;
1479
+ for (const part of remainingPath) {
1480
+ embeddedProp = embeddedProp?.embeddedProps?.[part];
1481
+ if (embeddedProp?.object && embeddedProp.fieldNames?.[0]) {
1482
+ return `${currentAlias}.${embeddedProp.fieldNames[0]}`;
1483
+ }
1484
+ }
1485
+ return `${currentAlias}.${embeddedProp?.fieldNames?.[0] ?? propName}`;
1486
+ }
1487
+ // Handle relations - auto-join if not the last part
1488
+ if (prop.kind === ReferenceKind.MANY_TO_ONE ||
1489
+ prop.kind === ReferenceKind.ONE_TO_ONE ||
1490
+ prop.kind === ReferenceKind.ONE_TO_MANY ||
1491
+ prop.kind === ReferenceKind.MANY_TO_MANY) {
1492
+ if (isLastPart) {
1493
+ return `${currentAlias}.${propName}`;
1494
+ }
1495
+ // Find existing join or create new one
1496
+ const joinPath = parts.slice(0, i + 1).join('.');
1497
+ const existingJoinKey = Object.keys(this.#state.joins).find(k => {
1498
+ const join = this.#state.joins[k];
1499
+ // Check by path or by key prefix (key format is `alias.field#joinAlias`)
1500
+ return join.path === joinPath || k.startsWith(`${currentAlias}.${propName}#`);
1501
+ });
1502
+ let joinAlias;
1503
+ if (existingJoinKey) {
1504
+ joinAlias = this.#state.joins[existingJoinKey].alias;
1505
+ }
1506
+ else {
1507
+ joinAlias = this.getNextAlias(prop.targetMeta?.className ?? propName);
1508
+ this.join(`${currentAlias}.${propName}`, joinAlias, {}, JoinType.leftJoin);
1509
+ }
1510
+ currentAlias = joinAlias;
1511
+ currentMeta = prop.targetMeta;
1512
+ continue;
1513
+ }
1514
+ // Scalar property - return it (if not last part, it's an invalid path but let SQL handle it)
1515
+ return `${currentAlias}.${propName}`;
1516
+ }
1517
+ return field;
1518
+ }
1519
+ init(type, data, cond) {
1520
+ this.ensureNotFinalized();
1521
+ this.#state.type = type;
1522
+ if ([QueryType.UPDATE, QueryType.DELETE].includes(type) && Utils.hasObjectKeys(this.#state.cond)) {
1523
+ throw new Error(`You are trying to call \`qb.where().${type.toLowerCase()}()\`. Calling \`qb.${type.toLowerCase()}()\` before \`qb.where()\` is required.`);
1524
+ }
1525
+ if (!this.helper.isTableNameAliasRequired(type)) {
1526
+ this.#state.fields = undefined;
1527
+ }
1528
+ if (data) {
1529
+ if (Utils.isEntity(data)) {
1530
+ data = this.em?.getComparator().prepareEntity(data) ?? serialize(data);
1531
+ }
1532
+ this.#state.data = this.helper.processData(data, this.#state.flags.has(QueryFlag.CONVERT_CUSTOM_TYPES), false);
1533
+ }
1534
+ if (cond) {
1535
+ this.where(cond);
1536
+ }
1537
+ return this;
1538
+ }
1539
+ getQueryBase(processVirtualEntity) {
1540
+ const qb = this.platform.createNativeQueryBuilder().setFlags(this.#state.flags);
1541
+ const { subQuery, aliasName, entityName, meta, rawTableName } = this.mainAlias;
1542
+ const requiresAlias = this.#state.finalized && (this.#state.explicitAlias || this.helper.isTableNameAliasRequired(this.type));
1543
+ const alias = requiresAlias ? aliasName : undefined;
1544
+ const schema = this.getSchema(this.mainAlias);
1545
+ const tableName = rawTableName
1546
+ ? rawTableName
1547
+ : subQuery instanceof NativeQueryBuilder
1548
+ ? subQuery.as(aliasName)
1549
+ : subQuery
1550
+ ? raw(`(${subQuery.sql}) as ${this.platform.quoteIdentifier(aliasName)}`, subQuery.params)
1551
+ : this.helper.getTableName(entityName);
1552
+ const joinSchema = this.#state.schema ?? this.em?.schema ?? schema;
1553
+ const schemaOverride = this.#state.schema ?? this.em?.schema;
1554
+ if (meta.virtual && processVirtualEntity) {
1555
+ qb.from(raw(this.fromVirtual(meta)), { indexHint: this.#state.indexHint });
1556
+ }
1557
+ else {
1558
+ qb.from(tableName, {
1559
+ schema: rawTableName ? undefined : schema,
1560
+ alias,
1561
+ indexHint: this.#state.indexHint,
1562
+ });
1563
+ }
1564
+ switch (this.type) {
1565
+ case QueryType.SELECT:
1566
+ qb.select(this.prepareFields(this.#state.fields, 'where', schema));
1567
+ if (this.#state.distinctOn) {
1568
+ qb.distinctOn(this.prepareFields(this.#state.distinctOn, 'where', schema));
1569
+ }
1570
+ else if (this.#state.flags.has(QueryFlag.DISTINCT)) {
1571
+ qb.distinct();
1572
+ }
1573
+ this.helper.processJoins(qb, this.#state.joins, joinSchema, schemaOverride);
1574
+ break;
1575
+ case QueryType.COUNT: {
1576
+ const fields = this.#state.fields.map(f => this.helper.mapper(f, this.type, undefined, undefined, schema));
1577
+ qb.count(fields, this.#state.flags.has(QueryFlag.DISTINCT));
1578
+ this.helper.processJoins(qb, this.#state.joins, joinSchema, schemaOverride);
1579
+ break;
1580
+ }
1581
+ case QueryType.INSERT:
1582
+ qb.insert(this.#state.data);
1583
+ break;
1584
+ case QueryType.UPDATE:
1585
+ qb.update(this.#state.data);
1586
+ this.helper.processJoins(qb, this.#state.joins, joinSchema, schemaOverride);
1587
+ this.helper.updateVersionProperty(qb, this.#state.data);
1588
+ break;
1589
+ case QueryType.DELETE:
1590
+ qb.delete();
1591
+ break;
1592
+ case QueryType.TRUNCATE:
1593
+ qb.truncate();
1594
+ break;
1595
+ }
1596
+ return qb;
1597
+ }
1598
+ applyDiscriminatorCondition() {
1599
+ const meta = this.mainAlias.meta;
1600
+ if (meta.root.inheritanceType !== 'sti' || !meta.discriminatorValue) {
1601
+ return;
1602
+ }
1603
+ const types = Object.values(meta.root.discriminatorMap).map(cls => this.metadata.get(cls));
1604
+ const children = [];
1605
+ const lookUpChildren = (ret, type) => {
1606
+ const children = types.filter(meta2 => meta2.extends === type);
1607
+ children.forEach(m => lookUpChildren(ret, m.class));
1608
+ ret.push(...children.filter(c => c.discriminatorValue));
1609
+ return children;
1610
+ };
1611
+ lookUpChildren(children, meta.class);
1612
+ this.andWhere({
1613
+ [meta.root.discriminatorColumn]: children.length > 0
1614
+ ? { $in: [meta.discriminatorValue, ...children.map(c => c.discriminatorValue)] }
1615
+ : meta.discriminatorValue,
1616
+ });
1617
+ }
1618
+ /**
1619
+ * Ensures TPT joins are applied. Can be called early before finalize() to populate
1620
+ * the _tptAlias map for use in join resolution. Safe to call multiple times.
1621
+ * @internal
1622
+ */
1623
+ ensureTPTJoins() {
1624
+ this.applyTPTJoins();
1625
+ }
1626
+ /**
1627
+ * For TPT (Table-Per-Type) inheritance: INNER JOINs parent tables.
1628
+ * When querying a child entity, we need to join all parent tables.
1629
+ * Field selection is handled separately in addTPTParentFields().
1630
+ */
1631
+ applyTPTJoins() {
1632
+ const meta = this.mainAlias.meta;
1633
+ if (meta?.inheritanceType !== 'tpt' ||
1634
+ !meta.tptParent ||
1635
+ ![QueryType.SELECT, QueryType.COUNT].includes(this.type)) {
1636
+ return;
1637
+ }
1638
+ if (this.#state.tptJoinsApplied) {
1639
+ return;
1640
+ }
1641
+ this.#state.tptJoinsApplied = true;
1642
+ let childMeta = meta;
1643
+ let childAlias = this.mainAlias.aliasName;
1644
+ while (childMeta.tptParent) {
1645
+ const parentMeta = childMeta.tptParent;
1646
+ const parentAlias = this.getNextAlias(parentMeta.className);
1647
+ this.createAlias(parentMeta.class, parentAlias);
1648
+ this.#state.tptAlias[parentMeta.className] = parentAlias;
1649
+ this.addPropertyJoin(childMeta.tptParentProp, childAlias, parentAlias, JoinType.innerJoin, `[tpt]${childMeta.className}`);
1650
+ childMeta = parentMeta;
1651
+ childAlias = parentAlias;
1652
+ }
1653
+ }
1654
+ /**
1655
+ * For TPT inheritance: adds field selections from parent tables.
1656
+ */
1657
+ addTPTParentFields() {
1658
+ const meta = this.mainAlias.meta;
1659
+ if (meta?.inheritanceType !== 'tpt' ||
1660
+ !meta.tptParent ||
1661
+ ![QueryType.SELECT, QueryType.COUNT].includes(this.type)) {
1662
+ return;
1663
+ }
1664
+ if (!this.#state.fields?.includes('*') && !this.#state.fields?.includes(`${this.mainAlias.aliasName}.*`)) {
1665
+ return;
1666
+ }
1667
+ let parentMeta = meta.tptParent;
1668
+ while (parentMeta) {
1669
+ const parentAlias = this.#state.tptAlias[parentMeta.className];
1670
+ if (parentAlias) {
1671
+ const schema = parentMeta.schema === '*' ? '*' : this.driver.getSchemaName(parentMeta);
1672
+ parentMeta
1673
+ .ownProps.filter(prop => this.platform.shouldHaveColumn(prop, []))
1674
+ .forEach(prop => this.#state.fields.push(...this.driver.mapPropToFieldNames(this, prop, parentAlias, parentMeta, schema)));
1675
+ }
1676
+ parentMeta = parentMeta.tptParent;
1677
+ }
1678
+ }
1679
+ /**
1680
+ * For TPT polymorphic queries: LEFT JOINs all child tables when querying a TPT base class.
1681
+ * Adds discriminator and child fields to determine and load the concrete type.
1682
+ */
1683
+ applyTPTPolymorphicJoins() {
1684
+ const meta = this.mainAlias.meta;
1685
+ const descendants = meta?.allTPTDescendants;
1686
+ if (!descendants?.length || ![QueryType.SELECT, QueryType.COUNT].includes(this.type)) {
1687
+ return;
1688
+ }
1689
+ if (!this.#state.fields?.includes('*') && !this.#state.fields?.includes(`${this.mainAlias.aliasName}.*`)) {
1690
+ return;
1691
+ }
1692
+ // LEFT JOIN each descendant table and add their fields
1693
+ for (const childMeta of descendants) {
1694
+ const childAlias = this.getNextAlias(childMeta.className);
1695
+ this.createAlias(childMeta.class, childAlias);
1696
+ this.#state.tptAlias[childMeta.className] = childAlias;
1697
+ this.addPropertyJoin(childMeta.tptInverseProp, this.mainAlias.aliasName, childAlias, JoinType.leftJoin, `[tpt]${meta.className}`);
1698
+ // Add child fields
1699
+ const schema = childMeta.schema === '*' ? '*' : this.driver.getSchemaName(childMeta);
1700
+ childMeta
1701
+ .ownProps.filter(prop => !prop.primary && this.platform.shouldHaveColumn(prop, []))
1702
+ .forEach(prop => this.#state.fields.push(...this.driver.mapPropToFieldNames(this, prop, childAlias, childMeta, schema)));
1703
+ }
1704
+ // Add computed discriminator (CASE WHEN to determine concrete type)
1705
+ // descendants is pre-sorted by depth (deepest first) during discovery
1706
+ if (meta.tptDiscriminatorColumn) {
1707
+ this.#state.fields.push(this.driver.buildTPTDiscriminatorExpression(meta, descendants, this.#state.tptAlias, this.mainAlias.aliasName));
1708
+ }
1709
+ }
1710
+ finalize() {
1711
+ if (this.#state.finalized) {
1712
+ return;
1713
+ }
1714
+ if (!this.#state.type) {
1715
+ this.select('*');
1716
+ }
1717
+ const meta = this.mainAlias.meta;
1718
+ this.applyDiscriminatorCondition();
1719
+ this.applyTPTJoins();
1720
+ this.addTPTParentFields();
1721
+ this.applyTPTPolymorphicJoins();
1722
+ this.processPopulateHint();
1723
+ this.processNestedJoins();
1724
+ if (meta && (this.#state.fields?.includes('*') || this.#state.fields?.includes(`${this.mainAlias.aliasName}.*`))) {
1725
+ const schema = this.getSchema(this.mainAlias);
1726
+ // Create a column mapping with unquoted aliases - quoting should be handled by the user via `quote` helper
1727
+ // For TPT, use helper to resolve correct alias per property (inherited props use parent alias)
1728
+ const quotedMainAlias = this.platform.quoteIdentifier(this.mainAlias.aliasName).toString();
1729
+ const columns = meta.createColumnMappingObject(prop => this.helper.getTPTAliasForProperty(prop.name, this.mainAlias.aliasName), quotedMainAlias);
1730
+ meta.props
1731
+ .filter(prop => prop.formula && (!prop.lazy || this.#state.flags.has(QueryFlag.INCLUDE_LAZY_FORMULAS)))
1732
+ .map(prop => {
1733
+ const aliased = this.platform.quoteIdentifier(prop.fieldNames[0]);
1734
+ const table = this.helper.createFormulaTable(quotedMainAlias, meta, schema);
1735
+ return `${this.driver.evaluateFormula(prop.formula, columns, table)} as ${aliased}`;
1736
+ })
1737
+ .filter(field => !this.#state.fields.some(f => {
1738
+ if (isRaw(f)) {
1739
+ return f.sql === field && f.params.length === 0;
1740
+ }
1741
+ return f === field;
1742
+ }))
1743
+ .forEach(field => this.#state.fields.push(raw(field)));
1744
+ }
1745
+ QueryHelper.processObjectParams(this.#state.data);
1746
+ QueryHelper.processObjectParams(this.#state.cond);
1747
+ QueryHelper.processObjectParams(this.#state.having);
1748
+ // automatically enable paginate flag when we detect to-many joins, but only if there is no `group by` clause
1749
+ if (!this.#state.flags.has(QueryFlag.DISABLE_PAGINATE) &&
1750
+ this.#state.groupBy.length === 0 &&
1751
+ this.hasToManyJoins()) {
1752
+ this.#state.flags.add(QueryFlag.PAGINATE);
1753
+ }
1754
+ if (meta &&
1755
+ !meta.virtual &&
1756
+ this.#state.flags.has(QueryFlag.PAGINATE) &&
1757
+ !this.#state.flags.has(QueryFlag.DISABLE_PAGINATE) &&
1758
+ (this.#state.limit > 0 || this.#state.offset > 0)) {
1759
+ this.wrapPaginateSubQuery(meta);
1760
+ }
1761
+ if (meta &&
1762
+ (this.#state.flags.has(QueryFlag.UPDATE_SUB_QUERY) || this.#state.flags.has(QueryFlag.DELETE_SUB_QUERY))) {
1763
+ this.wrapModifySubQuery(meta);
1764
+ }
1765
+ this.#state.finalized = true;
1766
+ }
1767
+ /** @internal */
1768
+ processPopulateHint() {
1769
+ if (this.#state.populateHintFinalized) {
1770
+ return;
1771
+ }
1772
+ const meta = this.mainAlias.meta;
1773
+ if (meta && this.#state.flags.has(QueryFlag.AUTO_JOIN_ONE_TO_ONE_OWNER)) {
1774
+ const relationsToPopulate = this.#state.populate.map(({ field }) => field);
1775
+ meta.relations
1776
+ .filter(prop => prop.kind === ReferenceKind.ONE_TO_ONE &&
1777
+ !prop.owner &&
1778
+ !relationsToPopulate.includes(prop.name) &&
1779
+ !relationsToPopulate.includes(`${prop.name}:ref`))
1780
+ .map(prop => ({ field: `${prop.name}:ref` }))
1781
+ .forEach(item => this.#state.populate.push(item));
1782
+ }
1783
+ this.#state.populate.forEach(({ field }) => {
1784
+ const [fromAlias, fromField] = this.helper.splitField(field);
1785
+ const aliasedField = `${fromAlias}.${fromField}`;
1786
+ const join = Object.keys(this.#state.joins).find(k => `${aliasedField}#${this.#state.joins[k].alias}` === k);
1787
+ if (join && this.#state.joins[join] && this.helper.isOneToOneInverse(fromField)) {
1788
+ this.#state.populateMap[join] = this.#state.joins[join].alias;
1789
+ return;
1790
+ }
1791
+ if (meta && this.helper.isOneToOneInverse(fromField)) {
1792
+ const prop = meta.properties[fromField];
1793
+ const alias = this.getNextAlias(prop.pivotEntity ?? prop.targetMeta.class);
1794
+ const aliasedName = `${fromAlias}.${prop.name}#${alias}`;
1795
+ this.#state.joins[aliasedName] = this.helper.joinOneToReference(prop, this.mainAlias.aliasName, alias, JoinType.leftJoin);
1796
+ this.#state.joins[aliasedName].path =
1797
+ `${Object.values(this.#state.joins).find(j => j.alias === fromAlias)?.path ?? meta.className}.${prop.name}`;
1798
+ this.#state.populateMap[aliasedName] = this.#state.joins[aliasedName].alias;
1799
+ this.createAlias(prop.targetMeta.class, alias);
1800
+ }
2066
1801
  });
1802
+ this.processPopulateWhere(false);
1803
+ this.processPopulateWhere(true);
1804
+ this.#state.populateHintFinalized = true;
1805
+ }
1806
+ processPopulateWhere(filter) {
1807
+ const value = filter ? this.#state.populateFilter : this.#state.populateWhere;
1808
+ if (value == null || value === PopulateHint.ALL) {
1809
+ return;
1810
+ }
1811
+ let joins = Object.values(this.#state.joins);
1812
+ for (const join of joins) {
1813
+ join.cond_ ??= join.cond;
1814
+ join.cond = { ...join.cond };
1815
+ }
1816
+ if (typeof value === 'object') {
1817
+ const cond = CriteriaNodeFactory.createNode(this.metadata, this.mainAlias.entityName, value).process(this, { matchPopulateJoins: true, ignoreBranching: true, preferNoBranch: true, filter });
1818
+ // there might be new joins created by processing the `populateWhere` object
1819
+ joins = Object.values(this.#state.joins);
1820
+ this.mergeOnConditions(joins, cond, filter);
1821
+ }
1822
+ }
1823
+ mergeOnConditions(joins, cond, filter, op) {
1824
+ for (const k of Object.keys(cond)) {
1825
+ if (Utils.isOperator(k)) {
1826
+ if (Array.isArray(cond[k])) {
1827
+ cond[k].forEach((c) => this.mergeOnConditions(joins, c, filter, k));
1828
+ }
1829
+ /* v8 ignore next */
1830
+ this.mergeOnConditions(joins, cond[k], filter, k);
1831
+ }
1832
+ const [alias] = this.helper.splitField(k);
1833
+ const join = joins.find(j => j.alias === alias);
1834
+ if (join) {
1835
+ const parentJoin = joins.find(j => j.alias === join.ownerAlias);
1836
+ // https://stackoverflow.com/a/56815807/3665878
1837
+ if (parentJoin && !filter) {
1838
+ const nested = (parentJoin.nested ??= new Set());
1839
+ join.type =
1840
+ join.type === JoinType.innerJoin ||
1841
+ [ReferenceKind.ONE_TO_MANY, ReferenceKind.MANY_TO_MANY].includes(parentJoin.prop.kind)
1842
+ ? JoinType.nestedInnerJoin
1843
+ : JoinType.nestedLeftJoin;
1844
+ nested.add(join);
1845
+ }
1846
+ if (join.cond[k]) {
1847
+ /* v8 ignore next */
1848
+ join.cond = { [op ?? '$and']: [join.cond, { [k]: cond[k] }] };
1849
+ }
1850
+ else if (op === '$or') {
1851
+ join.cond.$or ??= [];
1852
+ join.cond.$or.push({ [k]: cond[k] });
1853
+ }
1854
+ else {
1855
+ join.cond = { ...join.cond, [k]: cond[k] };
1856
+ }
1857
+ }
1858
+ }
1859
+ }
1860
+ /**
1861
+ * When adding an inner join on a left joined relation, we need to nest them,
1862
+ * otherwise the inner join could discard rows of the root table.
1863
+ */
1864
+ processNestedJoins() {
1865
+ if (this.#state.flags.has(QueryFlag.DISABLE_NESTED_INNER_JOIN)) {
1866
+ return;
1867
+ }
1868
+ const joins = Object.values(this.#state.joins);
1869
+ const lookupParentGroup = (j) => {
1870
+ return j.nested ?? (j.parent ? lookupParentGroup(j.parent) : undefined);
1871
+ };
1872
+ for (const join of joins) {
1873
+ if (join.type === JoinType.innerJoin) {
1874
+ join.parent = joins.find(j => j.alias === join.ownerAlias);
1875
+ // https://stackoverflow.com/a/56815807/3665878
1876
+ if (join.parent?.type === JoinType.leftJoin || join.parent?.type === JoinType.nestedLeftJoin) {
1877
+ const nested = (join.parent.nested ??= new Set());
1878
+ join.type = join.type === JoinType.innerJoin ? JoinType.nestedInnerJoin : JoinType.nestedLeftJoin;
1879
+ nested.add(join);
1880
+ }
1881
+ else if (join.parent?.type === JoinType.nestedInnerJoin) {
1882
+ const group = lookupParentGroup(join.parent);
1883
+ const nested = group ?? (join.parent.nested ??= new Set());
1884
+ join.type = join.type === JoinType.innerJoin ? JoinType.nestedInnerJoin : JoinType.nestedLeftJoin;
1885
+ nested.add(join);
1886
+ }
1887
+ }
1888
+ }
1889
+ }
1890
+ hasToManyJoins() {
1891
+ return Object.values(this.#state.joins).some(join => {
1892
+ return [ReferenceKind.ONE_TO_MANY, ReferenceKind.MANY_TO_MANY].includes(join.prop.kind);
1893
+ });
1894
+ }
1895
+ wrapPaginateSubQuery(meta) {
1896
+ const schema = this.getSchema(this.mainAlias);
1897
+ const pks = this.prepareFields(meta.primaryKeys, 'sub-query', schema);
1898
+ const subQuery = this.clone(['orderBy', 'fields', 'lockMode', 'lockTables'])
1899
+ .select(pks)
1900
+ .groupBy(pks)
1901
+ .limit(this.#state.limit);
1902
+ // revert the on conditions added via populateWhere, we want to apply those only once
1903
+ for (const join of Object.values(subQuery.#state.joins)) {
1904
+ if (join.cond_) {
1905
+ join.cond = join.cond_;
1906
+ }
1907
+ }
1908
+ if (this.#state.offset) {
1909
+ subQuery.offset(this.#state.offset);
1910
+ }
1911
+ const addToSelect = [];
1912
+ if (this.#state.orderBy.length > 0) {
1913
+ const orderBy = [];
1914
+ for (const orderMap of this.#state.orderBy) {
1915
+ for (const field of Utils.getObjectQueryKeys(orderMap)) {
1916
+ const direction = orderMap[field];
1917
+ if (RawQueryFragment.isKnownFragmentSymbol(field)) {
1918
+ orderBy.push({ [field]: direction });
1919
+ continue;
1920
+ }
1921
+ const [a, f] = this.helper.splitField(field);
1922
+ const prop = this.helper.getProperty(f, a);
1923
+ const type = this.platform.castColumn(prop);
1924
+ const fieldName = this.helper.mapper(field, this.type, undefined, null);
1925
+ if (!prop?.persist && !prop?.formula && !prop?.hasConvertToJSValueSQL && !pks.includes(fieldName)) {
1926
+ addToSelect.push(fieldName);
1927
+ }
1928
+ const quoted = this.platform.quoteIdentifier(fieldName);
1929
+ const key = raw(`min(${quoted}${type})`);
1930
+ orderBy.push({ [key]: direction });
1931
+ }
1932
+ }
1933
+ subQuery.orderBy(orderBy);
1934
+ }
1935
+ subQuery.#state.finalized = true;
1936
+ const innerQuery = subQuery.as(this.mainAlias.aliasName).clear('select').select(pks);
1937
+ if (addToSelect.length > 0) {
1938
+ addToSelect.forEach(prop => {
1939
+ const field = this.#state.fields.find(field => {
1940
+ if (typeof field === 'object' && field && '__as' in field) {
1941
+ return field.__as === prop;
1942
+ }
1943
+ if (isRaw(field)) {
1944
+ // not perfect, but should work most of the time, ideally we should check only the alias (`... as alias`)
1945
+ return field.sql.includes(prop);
1946
+ }
1947
+ return false;
1948
+ });
1949
+ /* v8 ignore next */
1950
+ if (isRaw(field)) {
1951
+ innerQuery.select(field);
1952
+ }
1953
+ else if (field instanceof NativeQueryBuilder) {
1954
+ innerQuery.select(field.toRaw());
1955
+ }
1956
+ else if (field) {
1957
+ innerQuery.select(field);
1958
+ }
1959
+ });
1960
+ }
1961
+ // multiple sub-queries are needed to get around mysql limitations with order by + limit + where in + group by (o.O)
1962
+ // https://stackoverflow.com/questions/17892762/mysql-this-version-of-mysql-doesnt-yet-support-limit-in-all-any-some-subqu
1963
+ const subSubQuery = this.platform.createNativeQueryBuilder();
1964
+ subSubQuery.select(pks).from(innerQuery);
1965
+ this.#state.limit = undefined;
1966
+ this.#state.offset = undefined;
1967
+ // Save the original WHERE conditions before pruning joins
1968
+ const originalCond = this.#state.cond;
1969
+ const populatePaths = this.getPopulatePaths();
1970
+ if (!this.#state.fields.some(field => isRaw(field))) {
1971
+ this.pruneJoinsForPagination(meta, populatePaths);
1972
+ }
1973
+ // Transfer WHERE conditions to ORDER BY joins (GH #6160)
1974
+ this.transferConditionsForOrderByJoins(meta, originalCond, populatePaths);
1975
+ const { sql, params } = subSubQuery.compile();
1976
+ this.select(this.#state.fields).where({
1977
+ [Utils.getPrimaryKeyHash(meta.primaryKeys)]: { $in: raw(sql, params) },
1978
+ });
1979
+ }
1980
+ /**
1981
+ * Computes the set of populate paths from the _populate hints.
1982
+ */
1983
+ getPopulatePaths() {
1984
+ const paths = new Set();
1985
+ function addPath(hints, prefix = '') {
1986
+ for (const hint of hints) {
1987
+ const field = hint.field.split(':')[0];
1988
+ const fullPath = prefix ? prefix + '.' + field : field;
1989
+ paths.add(fullPath);
1990
+ if (hint.children) {
1991
+ addPath(hint.children, fullPath);
1992
+ }
1993
+ }
1994
+ }
1995
+ addPath(this.#state.populate);
1996
+ return paths;
1997
+ }
1998
+ normalizeJoinPath(join, meta) {
1999
+ return join.path?.replace(/\[populate]|\[pivot]|:ref/g, '').replace(new RegExp(`^${meta.className}.`), '') ?? '';
2000
+ }
2001
+ /**
2002
+ * Transfers WHERE conditions to ORDER BY joins that are not used for population.
2003
+ * This ensures the outer query's ORDER BY uses the same filtered rows as the subquery.
2004
+ * GH #6160
2005
+ */
2006
+ transferConditionsForOrderByJoins(meta, cond, populatePaths) {
2007
+ if (!cond || this.#state.orderBy.length === 0) {
2008
+ return;
2009
+ }
2010
+ const orderByAliases = new Set(this.#state.orderBy
2011
+ .flatMap(hint => Object.keys(hint))
2012
+ .filter(k => !RawQueryFragment.isKnownFragmentSymbol(k))
2013
+ .map(k => k.split('.')[0]));
2014
+ for (const join of Object.values(this.#state.joins)) {
2015
+ const joinPath = this.normalizeJoinPath(join, meta);
2016
+ const isPopulateJoin = populatePaths.has(joinPath);
2017
+ // Only transfer conditions for joins used for ORDER BY but not for population
2018
+ if (orderByAliases.has(join.alias) && !isPopulateJoin) {
2019
+ this.transferConditionsToJoin(cond, join);
2020
+ }
2021
+ }
2022
+ }
2023
+ /**
2024
+ * Removes joins that are not used for population or ordering to improve performance.
2025
+ */
2026
+ pruneJoinsForPagination(meta, populatePaths) {
2027
+ const orderByAliases = this.#state.orderBy.flatMap(hint => Object.keys(hint)).map(k => k.split('.')[0]);
2028
+ const joins = Object.entries(this.#state.joins);
2029
+ const rootAlias = this.alias;
2030
+ function addParentAlias(alias) {
2031
+ const join = joins.find(j => j[1].alias === alias);
2032
+ if (join && join[1].ownerAlias !== rootAlias) {
2033
+ orderByAliases.push(join[1].ownerAlias);
2034
+ addParentAlias(join[1].ownerAlias);
2035
+ }
2036
+ }
2037
+ for (const orderByAlias of orderByAliases) {
2038
+ addParentAlias(orderByAlias);
2039
+ }
2040
+ for (const [key, join] of joins) {
2041
+ const path = this.normalizeJoinPath(join, meta);
2042
+ if (!populatePaths.has(path) && !orderByAliases.includes(join.alias)) {
2043
+ delete this.#state.joins[key];
2044
+ }
2045
+ }
2046
+ }
2047
+ /**
2048
+ * Transfers WHERE conditions that reference a join alias to the join's ON clause.
2049
+ * This is needed when a join is kept for ORDER BY after pagination wrapping,
2050
+ * so the outer query orders by the same filtered rows as the subquery.
2051
+ * @internal
2052
+ */
2053
+ transferConditionsToJoin(cond, join, path = '') {
2054
+ const aliasPrefix = join.alias + '.';
2055
+ for (const key of Object.keys(cond)) {
2056
+ const value = cond[key];
2057
+ // Handle $and/$or operators - recurse into nested conditions
2058
+ if (key === '$and' || key === '$or') {
2059
+ if (Array.isArray(value)) {
2060
+ for (const item of value) {
2061
+ this.transferConditionsToJoin(item, join, path);
2062
+ }
2063
+ }
2064
+ continue;
2065
+ }
2066
+ // Check if this condition references the join alias
2067
+ if (key.startsWith(aliasPrefix)) {
2068
+ // Add condition to the join's ON clause
2069
+ join.cond[key] = value;
2070
+ }
2071
+ }
2072
+ }
2073
+ wrapModifySubQuery(meta) {
2074
+ const subQuery = this.clone();
2075
+ subQuery.#state.finalized = true;
2076
+ // wrap one more time to get around MySQL limitations
2077
+ // https://stackoverflow.com/questions/45494/mysql-error-1093-cant-specify-target-table-for-update-in-from-clause
2078
+ const subSubQuery = this.platform.createNativeQueryBuilder();
2079
+ const method = this.#state.flags.has(QueryFlag.UPDATE_SUB_QUERY) ? 'update' : 'delete';
2080
+ const schema = this.getSchema(this.mainAlias);
2081
+ const pks = this.prepareFields(meta.primaryKeys, 'sub-query', schema);
2082
+ this.#state.cond = {}; // otherwise we would trigger validation error
2083
+ this.#state.joins = {}; // included in the subquery
2084
+ subSubQuery.select(pks).from(subQuery.as(this.mainAlias.aliasName));
2085
+ const { sql, params } = subSubQuery.compile();
2086
+ this[method](this.#state.data).where({
2087
+ [Utils.getPrimaryKeyHash(meta.primaryKeys)]: { $in: raw(sql, params) },
2088
+ });
2089
+ }
2090
+ getSchema(alias) {
2091
+ const { meta } = alias;
2092
+ const metaSchema = meta.schema && meta.schema !== '*' ? meta.schema : undefined;
2093
+ const schema = this.#state.schema ?? metaSchema ?? this.em?.schema ?? this.em?.config.getSchema(true);
2094
+ if (schema === this.platform.getDefaultSchemaName()) {
2095
+ return undefined;
2096
+ }
2097
+ return schema;
2098
+ }
2099
+ /** @internal */
2100
+ createAlias(entityName, aliasName, subQuery) {
2101
+ const meta = this.metadata.find(entityName);
2102
+ const alias = { aliasName, entityName, meta, subQuery };
2103
+ this.#state.aliases[aliasName] = alias;
2104
+ return alias;
2105
+ }
2106
+ createMainAlias(entityName, aliasName, subQuery) {
2107
+ this.#state.mainAlias = this.createAlias(entityName, aliasName, subQuery);
2108
+ this.#helper = this.createQueryBuilderHelper();
2109
+ return this.#state.mainAlias;
2110
+ }
2111
+ fromSubQuery(target, aliasName) {
2112
+ const { entityName } = target.mainAlias;
2113
+ aliasName ??= this.getNextAlias(entityName);
2114
+ const subQuery = target.#state.unionQuery ? target.toRaw() : target.getNativeQuery();
2115
+ this.createMainAlias(entityName, aliasName, subQuery);
2116
+ }
2117
+ fromEntityName(entityName, aliasName) {
2118
+ aliasName ??= this.#state.mainAlias?.aliasName ?? this.getNextAlias(entityName);
2119
+ this.createMainAlias(entityName, aliasName);
2120
+ }
2121
+ fromRawTable(tableName, aliasName) {
2122
+ aliasName ??= this.#state.mainAlias?.aliasName ?? this.getNextAlias(tableName);
2123
+ const meta = new EntityMetadata({
2124
+ className: tableName,
2125
+ collection: tableName,
2126
+ });
2127
+ meta.root = meta;
2128
+ this.#state.mainAlias = {
2129
+ aliasName,
2130
+ entityName: tableName,
2131
+ meta,
2132
+ rawTableName: tableName,
2133
+ };
2134
+ this.#state.aliases[aliasName] = this.#state.mainAlias;
2135
+ this.#helper = this.createQueryBuilderHelper();
2136
+ }
2137
+ createQueryBuilderHelper() {
2138
+ return new QueryBuilderHelper(this.mainAlias.entityName, this.mainAlias.aliasName, this.#state.aliases, this.#state.subQueries, this.driver, this.#state.tptAlias);
2139
+ }
2140
+ ensureFromClause() {
2067
2141
  /* v8 ignore next */
2068
- if (isRaw(field)) {
2069
- innerQuery.select(field);
2070
- } else if (field instanceof NativeQueryBuilder) {
2071
- innerQuery.select(field.toRaw());
2072
- } else if (field) {
2073
- innerQuery.select(field);
2074
- }
2075
- });
2076
- }
2077
- // multiple sub-queries are needed to get around mysql limitations with order by + limit + where in + group by (o.O)
2078
- // https://stackoverflow.com/questions/17892762/mysql-this-version-of-mysql-doesnt-yet-support-limit-in-all-any-some-subqu
2079
- const subSubQuery = this.platform.createNativeQueryBuilder();
2080
- subSubQuery.select(pks).from(innerQuery);
2081
- this.#state.limit = undefined;
2082
- this.#state.offset = undefined;
2083
- // Save the original WHERE conditions before pruning joins
2084
- const originalCond = this.#state.cond;
2085
- const populatePaths = this.getPopulatePaths();
2086
- if (!this.#state.fields.some(field => isRaw(field))) {
2087
- this.pruneJoinsForPagination(meta, populatePaths);
2088
- }
2089
- // Transfer WHERE conditions to ORDER BY joins (GH #6160)
2090
- this.transferConditionsForOrderByJoins(meta, originalCond, populatePaths);
2091
- const { sql, params } = subSubQuery.compile();
2092
- this.select(this.#state.fields).where({
2093
- [Utils.getPrimaryKeyHash(meta.primaryKeys)]: { $in: raw(sql, params) },
2094
- });
2095
- }
2096
- /**
2097
- * Computes the set of populate paths from the _populate hints.
2098
- */
2099
- getPopulatePaths() {
2100
- const paths = new Set();
2101
- function addPath(hints, prefix = '') {
2102
- for (const hint of hints) {
2103
- const field = hint.field.split(':')[0];
2104
- const fullPath = prefix ? prefix + '.' + field : field;
2105
- paths.add(fullPath);
2106
- if (hint.children) {
2107
- addPath(hint.children, fullPath);
2108
- }
2109
- }
2110
- }
2111
- addPath(this.#state.populate);
2112
- return paths;
2113
- }
2114
- normalizeJoinPath(join, meta) {
2115
- return join.path?.replace(/\[populate]|\[pivot]|:ref/g, '').replace(new RegExp(`^${meta.className}.`), '') ?? '';
2116
- }
2117
- /**
2118
- * Transfers WHERE conditions to ORDER BY joins that are not used for population.
2119
- * This ensures the outer query's ORDER BY uses the same filtered rows as the subquery.
2120
- * GH #6160
2121
- */
2122
- transferConditionsForOrderByJoins(meta, cond, populatePaths) {
2123
- if (!cond || this.#state.orderBy.length === 0) {
2124
- return;
2125
- }
2126
- const orderByAliases = new Set(
2127
- this.#state.orderBy
2128
- .flatMap(hint => Object.keys(hint))
2129
- .filter(k => !RawQueryFragment.isKnownFragmentSymbol(k))
2130
- .map(k => k.split('.')[0]),
2131
- );
2132
- for (const join of Object.values(this.#state.joins)) {
2133
- const joinPath = this.normalizeJoinPath(join, meta);
2134
- const isPopulateJoin = populatePaths.has(joinPath);
2135
- // Only transfer conditions for joins used for ORDER BY but not for population
2136
- if (orderByAliases.has(join.alias) && !isPopulateJoin) {
2137
- this.transferConditionsToJoin(cond, join);
2138
- }
2139
- }
2140
- }
2141
- /**
2142
- * Removes joins that are not used for population or ordering to improve performance.
2143
- */
2144
- pruneJoinsForPagination(meta, populatePaths) {
2145
- const orderByAliases = this.#state.orderBy.flatMap(hint => Object.keys(hint)).map(k => k.split('.')[0]);
2146
- const joins = Object.entries(this.#state.joins);
2147
- const rootAlias = this.alias;
2148
- function addParentAlias(alias) {
2149
- const join = joins.find(j => j[1].alias === alias);
2150
- if (join && join[1].ownerAlias !== rootAlias) {
2151
- orderByAliases.push(join[1].ownerAlias);
2152
- addParentAlias(join[1].ownerAlias);
2153
- }
2154
- }
2155
- for (const orderByAlias of orderByAliases) {
2156
- addParentAlias(orderByAlias);
2157
- }
2158
- for (const [key, join] of joins) {
2159
- const path = this.normalizeJoinPath(join, meta);
2160
- if (!populatePaths.has(path) && !orderByAliases.includes(join.alias)) {
2161
- delete this.#state.joins[key];
2162
- }
2163
- }
2164
- }
2165
- /**
2166
- * Transfers WHERE conditions that reference a join alias to the join's ON clause.
2167
- * This is needed when a join is kept for ORDER BY after pagination wrapping,
2168
- * so the outer query orders by the same filtered rows as the subquery.
2169
- * @internal
2170
- */
2171
- transferConditionsToJoin(cond, join, path = '') {
2172
- const aliasPrefix = join.alias + '.';
2173
- for (const key of Object.keys(cond)) {
2174
- const value = cond[key];
2175
- // Handle $and/$or operators - recurse into nested conditions
2176
- if (key === '$and' || key === '$or') {
2177
- if (Array.isArray(value)) {
2178
- for (const item of value) {
2179
- this.transferConditionsToJoin(item, join, path);
2180
- }
2181
- }
2182
- continue;
2183
- }
2184
- // Check if this condition references the join alias
2185
- if (key.startsWith(aliasPrefix)) {
2186
- // Add condition to the join's ON clause
2187
- join.cond[key] = value;
2188
- }
2189
- }
2190
- }
2191
- wrapModifySubQuery(meta) {
2192
- const subQuery = this.clone();
2193
- subQuery.#state.finalized = true;
2194
- // wrap one more time to get around MySQL limitations
2195
- // https://stackoverflow.com/questions/45494/mysql-error-1093-cant-specify-target-table-for-update-in-from-clause
2196
- const subSubQuery = this.platform.createNativeQueryBuilder();
2197
- const method = this.#state.flags.has(QueryFlag.UPDATE_SUB_QUERY) ? 'update' : 'delete';
2198
- const schema = this.getSchema(this.mainAlias);
2199
- const pks = this.prepareFields(meta.primaryKeys, 'sub-query', schema);
2200
- this.#state.cond = {}; // otherwise we would trigger validation error
2201
- this.#state.joins = {}; // included in the subquery
2202
- subSubQuery.select(pks).from(subQuery.as(this.mainAlias.aliasName));
2203
- const { sql, params } = subSubQuery.compile();
2204
- this[method](this.#state.data).where({
2205
- [Utils.getPrimaryKeyHash(meta.primaryKeys)]: { $in: raw(sql, params) },
2206
- });
2207
- }
2208
- getSchema(alias) {
2209
- const { meta } = alias;
2210
- const metaSchema = meta.schema && meta.schema !== '*' ? meta.schema : undefined;
2211
- const schema = this.#state.schema ?? metaSchema ?? this.em?.schema ?? this.em?.config.getSchema(true);
2212
- if (schema === this.platform.getDefaultSchemaName()) {
2213
- return undefined;
2214
- }
2215
- return schema;
2216
- }
2217
- /** @internal */
2218
- createAlias(entityName, aliasName, subQuery) {
2219
- const meta = this.metadata.find(entityName);
2220
- const alias = { aliasName, entityName, meta, subQuery };
2221
- this.#state.aliases[aliasName] = alias;
2222
- return alias;
2223
- }
2224
- createMainAlias(entityName, aliasName, subQuery) {
2225
- this.#state.mainAlias = this.createAlias(entityName, aliasName, subQuery);
2226
- this.#helper = this.createQueryBuilderHelper();
2227
- return this.#state.mainAlias;
2228
- }
2229
- fromSubQuery(target, aliasName) {
2230
- const { entityName } = target.mainAlias;
2231
- aliasName ??= this.getNextAlias(entityName);
2232
- const subQuery = target.#state.unionQuery ? target.toRaw() : target.getNativeQuery();
2233
- this.createMainAlias(entityName, aliasName, subQuery);
2234
- }
2235
- fromEntityName(entityName, aliasName) {
2236
- aliasName ??= this.#state.mainAlias?.aliasName ?? this.getNextAlias(entityName);
2237
- this.createMainAlias(entityName, aliasName);
2238
- }
2239
- fromRawTable(tableName, aliasName) {
2240
- aliasName ??= this.#state.mainAlias?.aliasName ?? this.getNextAlias(tableName);
2241
- const meta = new EntityMetadata({
2242
- className: tableName,
2243
- collection: tableName,
2244
- });
2245
- meta.root = meta;
2246
- this.#state.mainAlias = {
2247
- aliasName,
2248
- entityName: tableName,
2249
- meta,
2250
- rawTableName: tableName,
2251
- };
2252
- this.#state.aliases[aliasName] = this.#state.mainAlias;
2253
- this.#helper = this.createQueryBuilderHelper();
2254
- }
2255
- createQueryBuilderHelper() {
2256
- return new QueryBuilderHelper(
2257
- this.mainAlias.entityName,
2258
- this.mainAlias.aliasName,
2259
- this.#state.aliases,
2260
- this.#state.subQueries,
2261
- this.driver,
2262
- this.#state.tptAlias,
2263
- );
2264
- }
2265
- ensureFromClause() {
2142
+ if (!this.#state.mainAlias) {
2143
+ throw new Error(`Cannot proceed to build a query because the main alias is not set.`);
2144
+ }
2145
+ }
2146
+ ensureNotFinalized() {
2147
+ if (this.#state.finalized) {
2148
+ throw new Error('This QueryBuilder instance is already finalized, clone it first if you want to modify it.');
2149
+ }
2150
+ }
2151
+ /** @ignore */
2266
2152
  /* v8 ignore next */
2267
- if (!this.#state.mainAlias) {
2268
- throw new Error(`Cannot proceed to build a query because the main alias is not set.`);
2269
- }
2270
- }
2271
- ensureNotFinalized() {
2272
- if (this.#state.finalized) {
2273
- throw new Error('This QueryBuilder instance is already finalized, clone it first if you want to modify it.');
2274
- }
2275
- }
2276
- /** @ignore */
2277
- /* v8 ignore next */
2278
- [Symbol.for('nodejs.util.inspect.custom')](depth = 2) {
2279
- const object = { ...this };
2280
- const hidden = ['metadata', 'driver', 'context', 'platform', 'type'];
2281
- Object.keys(object)
2282
- .filter(k => k.startsWith('_'))
2283
- .forEach(k => delete object[k]);
2284
- Object.keys(object)
2285
- .filter(k => object[k] == null)
2286
- .forEach(k => delete object[k]);
2287
- hidden.forEach(k => delete object[k]);
2288
- let prefix = this.type ? this.type.substring(0, 1) + this.type.toLowerCase().substring(1) : '';
2289
- if (this.#state.data) {
2290
- object.data = this.#state.data;
2291
- }
2292
- if (this.#state.schema) {
2293
- object.schema = this.#state.schema;
2294
- }
2295
- if (!Utils.isEmpty(this.#state.cond)) {
2296
- object.where = this.#state.cond;
2297
- }
2298
- if (this.#state.onConflict?.[0]) {
2299
- prefix = 'Upsert';
2300
- object.onConflict = this.#state.onConflict[0];
2301
- }
2302
- if (!Utils.isEmpty(this.#state.orderBy)) {
2303
- object.orderBy = this.#state.orderBy;
2304
- }
2305
- const name = this.#state.mainAlias
2306
- ? `${prefix}QueryBuilder<${Utils.className(this.#state.mainAlias?.entityName)}>`
2307
- : 'QueryBuilder';
2308
- const ret = inspect(object, { depth });
2309
- return ret === '[Object]' ? `[${name}]` : name + ' ' + ret;
2310
- }
2153
+ [Symbol.for('nodejs.util.inspect.custom')](depth = 2) {
2154
+ const object = { ...this };
2155
+ const hidden = ['metadata', 'driver', 'context', 'platform', 'type'];
2156
+ Object.keys(object)
2157
+ .filter(k => k.startsWith('_'))
2158
+ .forEach(k => delete object[k]);
2159
+ Object.keys(object)
2160
+ .filter(k => object[k] == null)
2161
+ .forEach(k => delete object[k]);
2162
+ hidden.forEach(k => delete object[k]);
2163
+ let prefix = this.type ? this.type.substring(0, 1) + this.type.toLowerCase().substring(1) : '';
2164
+ if (this.#state.data) {
2165
+ object.data = this.#state.data;
2166
+ }
2167
+ if (this.#state.schema) {
2168
+ object.schema = this.#state.schema;
2169
+ }
2170
+ if (!Utils.isEmpty(this.#state.cond)) {
2171
+ object.where = this.#state.cond;
2172
+ }
2173
+ if (this.#state.onConflict?.[0]) {
2174
+ prefix = 'Upsert';
2175
+ object.onConflict = this.#state.onConflict[0];
2176
+ }
2177
+ if (!Utils.isEmpty(this.#state.orderBy)) {
2178
+ object.orderBy = this.#state.orderBy;
2179
+ }
2180
+ const name = this.#state.mainAlias
2181
+ ? `${prefix}QueryBuilder<${Utils.className(this.#state.mainAlias?.entityName)}>`
2182
+ : 'QueryBuilder';
2183
+ const ret = inspect(object, { depth });
2184
+ return ret === '[Object]' ? `[${name}]` : name + ' ' + ret;
2185
+ }
2311
2186
  }
2312
2187
  _a = QueryBuilder;