@mikro-orm/sql 7.0.2-dev.9 → 7.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AbstractSqlConnection.d.ts +95 -47
- package/AbstractSqlConnection.js +240 -232
- package/AbstractSqlDriver.d.ts +412 -155
- package/AbstractSqlDriver.js +2062 -1937
- package/AbstractSqlPlatform.d.ts +84 -73
- package/AbstractSqlPlatform.js +163 -158
- package/PivotCollectionPersister.d.ts +33 -15
- package/PivotCollectionPersister.js +158 -160
- package/README.md +128 -294
- package/SqlEntityManager.d.ts +68 -20
- package/SqlEntityManager.js +54 -37
- package/SqlEntityRepository.d.ts +15 -14
- package/SqlEntityRepository.js +24 -23
- package/dialects/mssql/MsSqlNativeQueryBuilder.d.ts +12 -12
- package/dialects/mssql/MsSqlNativeQueryBuilder.js +192 -194
- package/dialects/mysql/BaseMySqlPlatform.d.ts +64 -45
- package/dialects/mysql/BaseMySqlPlatform.js +134 -131
- package/dialects/mysql/MySqlExceptionConverter.d.ts +6 -6
- package/dialects/mysql/MySqlExceptionConverter.js +91 -77
- package/dialects/mysql/MySqlNativeQueryBuilder.d.ts +3 -3
- package/dialects/mysql/MySqlNativeQueryBuilder.js +66 -69
- package/dialects/mysql/MySqlSchemaHelper.d.ts +39 -39
- package/dialects/mysql/MySqlSchemaHelper.js +327 -319
- package/dialects/oracledb/OracleDialect.d.ts +81 -52
- package/dialects/oracledb/OracleDialect.js +155 -149
- package/dialects/oracledb/OracleNativeQueryBuilder.d.ts +12 -12
- package/dialects/oracledb/OracleNativeQueryBuilder.js +232 -236
- package/dialects/postgresql/BasePostgreSqlPlatform.d.ts +108 -105
- package/dialects/postgresql/BasePostgreSqlPlatform.js +351 -350
- package/dialects/postgresql/FullTextType.d.ts +10 -6
- package/dialects/postgresql/FullTextType.js +51 -51
- package/dialects/postgresql/PostgreSqlExceptionConverter.d.ts +5 -5
- package/dialects/postgresql/PostgreSqlExceptionConverter.js +55 -43
- package/dialects/postgresql/PostgreSqlNativeQueryBuilder.d.ts +1 -1
- package/dialects/postgresql/PostgreSqlNativeQueryBuilder.js +4 -4
- package/dialects/postgresql/PostgreSqlSchemaHelper.d.ts +102 -82
- package/dialects/postgresql/PostgreSqlSchemaHelper.js +711 -683
- package/dialects/sqlite/BaseSqliteConnection.d.ts +3 -5
- package/dialects/sqlite/BaseSqliteConnection.js +21 -19
- package/dialects/sqlite/NodeSqliteDialect.d.ts +1 -1
- package/dialects/sqlite/NodeSqliteDialect.js +23 -23
- package/dialects/sqlite/SqliteDriver.d.ts +1 -1
- package/dialects/sqlite/SqliteDriver.js +3 -3
- package/dialects/sqlite/SqliteExceptionConverter.d.ts +6 -6
- package/dialects/sqlite/SqliteExceptionConverter.js +67 -51
- package/dialects/sqlite/SqliteNativeQueryBuilder.d.ts +2 -2
- package/dialects/sqlite/SqliteNativeQueryBuilder.js +7 -7
- package/dialects/sqlite/SqlitePlatform.d.ts +63 -72
- package/dialects/sqlite/SqlitePlatform.js +139 -139
- package/dialects/sqlite/SqliteSchemaHelper.d.ts +70 -60
- package/dialects/sqlite/SqliteSchemaHelper.js +533 -520
- package/package.json +4 -4
- package/plugin/index.d.ts +44 -35
- package/plugin/index.js +44 -36
- package/plugin/transformer.d.ts +117 -94
- package/plugin/transformer.js +890 -881
- package/query/ArrayCriteriaNode.d.ts +4 -4
- package/query/ArrayCriteriaNode.js +18 -18
- package/query/CriteriaNode.d.ts +35 -25
- package/query/CriteriaNode.js +133 -123
- package/query/CriteriaNodeFactory.d.ts +49 -6
- package/query/CriteriaNodeFactory.js +97 -94
- package/query/NativeQueryBuilder.d.ts +120 -117
- package/query/NativeQueryBuilder.js +484 -480
- package/query/ObjectCriteriaNode.d.ts +12 -12
- package/query/ObjectCriteriaNode.js +298 -282
- package/query/QueryBuilder.d.ts +1546 -904
- package/query/QueryBuilder.js +2270 -2145
- package/query/QueryBuilderHelper.d.ts +153 -72
- package/query/QueryBuilderHelper.js +1079 -1028
- package/query/ScalarCriteriaNode.d.ts +3 -3
- package/query/ScalarCriteriaNode.js +53 -46
- package/query/enums.d.ts +16 -14
- package/query/enums.js +16 -14
- package/query/raw.d.ts +16 -6
- package/query/raw.js +10 -10
- package/schema/DatabaseSchema.d.ts +73 -50
- package/schema/DatabaseSchema.js +331 -307
- package/schema/DatabaseTable.d.ts +96 -73
- package/schema/DatabaseTable.js +1012 -927
- package/schema/SchemaComparator.d.ts +58 -54
- package/schema/SchemaComparator.js +745 -719
- package/schema/SchemaHelper.d.ts +110 -80
- package/schema/SchemaHelper.js +676 -645
- package/schema/SqlSchemaGenerator.d.ts +79 -58
- package/schema/SqlSchemaGenerator.js +536 -501
- package/typings.d.ts +380 -266
package/query/QueryBuilder.js
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
var _a;
|
|
2
|
-
import {
|
|
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';
|
|
3
20
|
import { JoinType, QueryType } from './enums.js';
|
|
4
21
|
import { QueryBuilderHelper } from './QueryBuilderHelper.js';
|
|
5
22
|
import { CriteriaNodeFactory } from './CriteriaNodeFactory.js';
|
|
@@ -26,2162 +43,2270 @@ const FIELD_ALIAS_RE = /^(.+?)\s+as\s+(\w+)$/i;
|
|
|
26
43
|
* ```
|
|
27
44
|
*/
|
|
28
45
|
export class QueryBuilder {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
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
|
-
}));
|
|
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];
|
|
500
327
|
});
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
if (
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
}
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
}
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
this.#state.
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
this.
|
|
679
|
-
this
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
this
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
}
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
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 `/* ... *‍/`. Some characters are forbidden such as `/*, *‍/` 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 `/*+ ... *‍/`. 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) {
|
|
738
752
|
this.#query = {};
|
|
739
|
-
this.
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
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)];
|
|
791
850
|
}
|
|
792
|
-
return;
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
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)}`;
|
|
809
1310
|
}
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
}
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
}
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
if (
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
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);
|
|
963
1474
|
}
|
|
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]));
|
|
1475
|
+
}
|
|
1012
1476
|
};
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
}
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
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,
|
|
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}#`);
|
|
1197
1574
|
});
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
qb
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
this.
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
this.#state.
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
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)) {
|
|
1307
1903
|
const prop = meta.properties[fromField];
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
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;
|
|
1316
1915
|
this.createAlias(prop.targetMeta.class, alias);
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
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;
|
|
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));
|
|
1713
1949
|
}
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
}
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
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,
|
|
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;
|
|
2126
2066
|
});
|
|
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() {
|
|
2141
2067
|
/* v8 ignore next */
|
|
2142
|
-
if (
|
|
2143
|
-
|
|
2144
|
-
}
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
}
|
|
2151
|
-
|
|
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() {
|
|
2152
2266
|
/* v8 ignore next */
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
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
|
+
}
|
|
2186
2311
|
}
|
|
2187
2312
|
_a = QueryBuilder;
|