@mikro-orm/sql 7.0.0-rc.2 → 7.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AbstractSqlConnection.d.ts +5 -4
- package/AbstractSqlConnection.js +20 -6
- package/AbstractSqlDriver.d.ts +19 -13
- package/AbstractSqlDriver.js +225 -47
- package/AbstractSqlPlatform.d.ts +35 -0
- package/AbstractSqlPlatform.js +51 -5
- package/PivotCollectionPersister.d.ts +2 -11
- package/PivotCollectionPersister.js +59 -59
- package/README.md +5 -4
- package/SqlEntityManager.d.ts +2 -2
- package/SqlEntityManager.js +5 -5
- package/dialects/index.d.ts +1 -0
- package/dialects/index.js +1 -0
- package/dialects/mssql/MsSqlNativeQueryBuilder.d.ts +2 -0
- package/dialects/mssql/MsSqlNativeQueryBuilder.js +8 -4
- package/dialects/mysql/BaseMySqlPlatform.d.ts +6 -0
- package/dialects/mysql/BaseMySqlPlatform.js +18 -2
- package/dialects/mysql/MySqlSchemaHelper.d.ts +1 -1
- package/dialects/mysql/MySqlSchemaHelper.js +25 -14
- package/dialects/oracledb/OracleDialect.d.ts +78 -0
- package/dialects/oracledb/OracleDialect.js +166 -0
- package/dialects/oracledb/OracleNativeQueryBuilder.d.ts +19 -0
- package/dialects/oracledb/OracleNativeQueryBuilder.js +249 -0
- package/dialects/oracledb/index.d.ts +2 -0
- package/dialects/oracledb/index.js +2 -0
- package/dialects/postgresql/BasePostgreSqlPlatform.d.ts +6 -0
- package/dialects/postgresql/BasePostgreSqlPlatform.js +49 -37
- package/dialects/postgresql/PostgreSqlSchemaHelper.js +75 -59
- package/dialects/sqlite/BaseSqliteConnection.js +2 -2
- package/dialects/sqlite/NodeSqliteDialect.js +3 -1
- package/dialects/sqlite/SqlitePlatform.d.ts +1 -0
- package/dialects/sqlite/SqlitePlatform.js +7 -1
- package/dialects/sqlite/SqliteSchemaHelper.js +23 -17
- package/index.d.ts +1 -1
- package/index.js +0 -1
- package/package.json +30 -30
- package/plugin/index.d.ts +1 -14
- package/plugin/index.js +13 -13
- package/plugin/transformer.d.ts +6 -22
- package/plugin/transformer.js +91 -82
- package/query/ArrayCriteriaNode.d.ts +1 -1
- package/query/CriteriaNode.js +28 -10
- package/query/CriteriaNodeFactory.js +20 -4
- package/query/NativeQueryBuilder.d.ts +28 -3
- package/query/NativeQueryBuilder.js +65 -3
- package/query/ObjectCriteriaNode.js +75 -31
- package/query/QueryBuilder.d.ts +199 -100
- package/query/QueryBuilder.js +544 -358
- package/query/QueryBuilderHelper.d.ts +18 -14
- package/query/QueryBuilderHelper.js +364 -147
- package/query/ScalarCriteriaNode.js +17 -8
- package/query/enums.d.ts +2 -0
- package/query/enums.js +2 -0
- package/query/raw.js +1 -1
- package/schema/DatabaseSchema.d.ts +7 -5
- package/schema/DatabaseSchema.js +68 -45
- package/schema/DatabaseTable.d.ts +8 -6
- package/schema/DatabaseTable.js +191 -107
- package/schema/SchemaComparator.d.ts +1 -3
- package/schema/SchemaComparator.js +76 -50
- package/schema/SchemaHelper.d.ts +2 -13
- package/schema/SchemaHelper.js +30 -9
- package/schema/SqlSchemaGenerator.d.ts +4 -14
- package/schema/SqlSchemaGenerator.js +26 -12
- package/typings.d.ts +10 -5
- package/tsconfig.build.tsbuildinfo +0 -1
package/query/QueryBuilder.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
|
|
1
|
+
var _a;
|
|
2
|
+
import { EntityMetadata, helper, inspect, isRaw, LoadStrategy, LockMode, PopulateHint, QueryFlag, QueryHelper, raw, RawQueryFragment, Reference, ReferenceKind, serialize, Utils, ValidationError, } from '@mikro-orm/core';
|
|
2
3
|
import { JoinType, QueryType } from './enums.js';
|
|
3
4
|
import { QueryBuilderHelper } from './QueryBuilderHelper.js';
|
|
4
5
|
import { CriteriaNodeFactory } from './CriteriaNodeFactory.js';
|
|
@@ -31,59 +32,54 @@ export class QueryBuilder {
|
|
|
31
32
|
connectionType;
|
|
32
33
|
em;
|
|
33
34
|
loggerContext;
|
|
35
|
+
#state = _a.createDefaultState();
|
|
36
|
+
#helper;
|
|
37
|
+
#query;
|
|
38
|
+
/** @internal */
|
|
39
|
+
static createDefaultState() {
|
|
40
|
+
return {
|
|
41
|
+
aliasCounter: 0,
|
|
42
|
+
explicitAlias: false,
|
|
43
|
+
populateHintFinalized: false,
|
|
44
|
+
joins: {},
|
|
45
|
+
cond: {},
|
|
46
|
+
orderBy: [],
|
|
47
|
+
groupBy: [],
|
|
48
|
+
having: {},
|
|
49
|
+
comments: [],
|
|
50
|
+
hintComments: [],
|
|
51
|
+
subQueries: {},
|
|
52
|
+
aliases: {},
|
|
53
|
+
tptAlias: {},
|
|
54
|
+
ctes: [],
|
|
55
|
+
tptJoinsApplied: false,
|
|
56
|
+
autoJoinedPaths: [],
|
|
57
|
+
populate: [],
|
|
58
|
+
populateMap: {},
|
|
59
|
+
flags: new Set([QueryFlag.CONVERT_CUSTOM_TYPES]),
|
|
60
|
+
finalized: false,
|
|
61
|
+
joinedProps: new Map(),
|
|
62
|
+
};
|
|
63
|
+
}
|
|
34
64
|
get mainAlias() {
|
|
35
65
|
this.ensureFromClause();
|
|
36
|
-
return this.
|
|
66
|
+
return this.#state.mainAlias;
|
|
37
67
|
}
|
|
38
68
|
get alias() {
|
|
39
69
|
return this.mainAlias.aliasName;
|
|
40
70
|
}
|
|
41
71
|
get helper() {
|
|
42
72
|
this.ensureFromClause();
|
|
43
|
-
return this
|
|
73
|
+
return this.#helper;
|
|
44
74
|
}
|
|
45
75
|
get type() {
|
|
46
|
-
return this.
|
|
76
|
+
return this.#state.type ?? QueryType.SELECT;
|
|
47
77
|
}
|
|
48
78
|
/** @internal */
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
aliasCounter = 0;
|
|
53
|
-
flags = new Set([QueryFlag.CONVERT_CUSTOM_TYPES]);
|
|
54
|
-
finalized = false;
|
|
55
|
-
populateHintFinalized = false;
|
|
56
|
-
_joins = {};
|
|
57
|
-
_explicitAlias = false;
|
|
58
|
-
_schema;
|
|
59
|
-
_cond = {};
|
|
60
|
-
_data;
|
|
61
|
-
_orderBy = [];
|
|
62
|
-
_groupBy = [];
|
|
63
|
-
_having = {};
|
|
64
|
-
_returning;
|
|
65
|
-
_onConflict;
|
|
66
|
-
_limit;
|
|
67
|
-
_offset;
|
|
68
|
-
_distinctOn;
|
|
69
|
-
_joinedProps = new Map();
|
|
70
|
-
_cache;
|
|
71
|
-
_indexHint;
|
|
72
|
-
_collation;
|
|
73
|
-
_comments = [];
|
|
74
|
-
_hintComments = [];
|
|
75
|
-
flushMode;
|
|
76
|
-
lockMode;
|
|
77
|
-
lockTables;
|
|
78
|
-
subQueries = {};
|
|
79
|
-
_mainAlias;
|
|
80
|
-
_aliases = {};
|
|
81
|
-
_tptAlias = {}; // maps entity className to alias for TPT parent tables
|
|
82
|
-
_helper;
|
|
83
|
-
_query;
|
|
79
|
+
get state() {
|
|
80
|
+
return this.#state;
|
|
81
|
+
}
|
|
84
82
|
platform;
|
|
85
|
-
tptJoinsApplied = false;
|
|
86
|
-
autoJoinedPaths = [];
|
|
87
83
|
/**
|
|
88
84
|
* @internal
|
|
89
85
|
*/
|
|
@@ -96,15 +92,15 @@ export class QueryBuilder {
|
|
|
96
92
|
this.loggerContext = loggerContext;
|
|
97
93
|
this.platform = this.driver.getPlatform();
|
|
98
94
|
if (alias) {
|
|
99
|
-
this.aliasCounter++;
|
|
100
|
-
this.
|
|
95
|
+
this.#state.aliasCounter++;
|
|
96
|
+
this.#state.explicitAlias = true;
|
|
101
97
|
}
|
|
102
98
|
// @ts-expect-error union type does not match the overloaded method signature
|
|
103
99
|
this.from(entityName, alias);
|
|
104
100
|
}
|
|
105
101
|
select(fields, distinct = false) {
|
|
106
102
|
this.ensureNotFinalized();
|
|
107
|
-
this.
|
|
103
|
+
this.#state.fields = Utils.asArray(fields).flatMap(f => {
|
|
108
104
|
if (typeof f !== 'string') {
|
|
109
105
|
// Normalize sql.ref('prop') and sql.ref('prop').as('alias') to string form
|
|
110
106
|
if (isRaw(f) && f.sql === '??' && f.params.length === 1) {
|
|
@@ -115,14 +111,14 @@ export class QueryBuilder {
|
|
|
115
111
|
}
|
|
116
112
|
return f;
|
|
117
113
|
}
|
|
118
|
-
const asMatch =
|
|
114
|
+
const asMatch = FIELD_ALIAS_RE.exec(f);
|
|
119
115
|
if (asMatch) {
|
|
120
116
|
return `${this.resolveNestedPath(asMatch[1].trim())} as ${asMatch[2]}`;
|
|
121
117
|
}
|
|
122
118
|
return this.resolveNestedPath(f);
|
|
123
119
|
});
|
|
124
120
|
if (distinct) {
|
|
125
|
-
this.flags.add(QueryFlag.DISTINCT);
|
|
121
|
+
this.#state.flags.add(QueryFlag.DISTINCT);
|
|
126
122
|
}
|
|
127
123
|
return this.init(QueryType.SELECT);
|
|
128
124
|
}
|
|
@@ -131,10 +127,10 @@ export class QueryBuilder {
|
|
|
131
127
|
*/
|
|
132
128
|
addSelect(fields) {
|
|
133
129
|
this.ensureNotFinalized();
|
|
134
|
-
if (this.
|
|
130
|
+
if (this.#state.type && this.#state.type !== QueryType.SELECT) {
|
|
135
131
|
return this;
|
|
136
132
|
}
|
|
137
|
-
return this.select([...Utils.asArray(this.
|
|
133
|
+
return this.select([...Utils.asArray(this.#state.fields), ...Utils.asArray(fields)]);
|
|
138
134
|
}
|
|
139
135
|
distinct() {
|
|
140
136
|
this.ensureNotFinalized();
|
|
@@ -142,7 +138,7 @@ export class QueryBuilder {
|
|
|
142
138
|
}
|
|
143
139
|
distinctOn(fields) {
|
|
144
140
|
this.ensureNotFinalized();
|
|
145
|
-
this.
|
|
141
|
+
this.#state.distinctOn = Utils.asArray(fields);
|
|
146
142
|
return this;
|
|
147
143
|
}
|
|
148
144
|
/**
|
|
@@ -217,16 +213,16 @@ export class QueryBuilder {
|
|
|
217
213
|
*/
|
|
218
214
|
count(field, distinct = false) {
|
|
219
215
|
if (field) {
|
|
220
|
-
this.
|
|
216
|
+
this.#state.fields = Utils.asArray(field);
|
|
221
217
|
}
|
|
222
218
|
else if (distinct || this.hasToManyJoins()) {
|
|
223
|
-
this.
|
|
219
|
+
this.#state.fields = this.mainAlias.meta.primaryKeys;
|
|
224
220
|
}
|
|
225
221
|
else {
|
|
226
|
-
this.
|
|
222
|
+
this.#state.fields = [raw('*')];
|
|
227
223
|
}
|
|
228
224
|
if (distinct) {
|
|
229
|
-
this.flags.add(QueryFlag.DISTINCT);
|
|
225
|
+
this.#state.flags.add(QueryFlag.DISTINCT);
|
|
230
226
|
}
|
|
231
227
|
return this.init(QueryType.COUNT);
|
|
232
228
|
}
|
|
@@ -260,29 +256,30 @@ export class QueryBuilder {
|
|
|
260
256
|
* ```
|
|
261
257
|
*/
|
|
262
258
|
joinAndSelect(field, alias, cond = {}, type = JoinType.innerJoin, path, fields, schema) {
|
|
263
|
-
if (!this.
|
|
259
|
+
if (!this.#state.type) {
|
|
264
260
|
this.select('*');
|
|
265
261
|
}
|
|
266
262
|
let subquery;
|
|
267
263
|
if (Array.isArray(field)) {
|
|
268
|
-
const rawFragment = field[1] instanceof
|
|
264
|
+
const rawFragment = field[1] instanceof _a ? field[1].toRaw() : field[1];
|
|
269
265
|
subquery = this.platform.formatQuery(rawFragment.sql, rawFragment.params);
|
|
270
266
|
field = field[0];
|
|
271
267
|
}
|
|
272
268
|
const { prop, key } = this.joinReference(field, alias, cond, type, path, schema, subquery);
|
|
273
269
|
const [fromAlias] = this.helper.splitField(field);
|
|
274
270
|
if (subquery) {
|
|
275
|
-
this.
|
|
271
|
+
this.#state.joins[key].subquery = subquery;
|
|
276
272
|
}
|
|
277
|
-
const populate = this.
|
|
273
|
+
const populate = this.#state.joinedProps.get(fromAlias);
|
|
278
274
|
const item = { field: prop.name, strategy: LoadStrategy.JOINED, children: [] };
|
|
279
275
|
if (populate) {
|
|
280
276
|
populate.children.push(item);
|
|
281
277
|
}
|
|
282
|
-
else {
|
|
283
|
-
|
|
278
|
+
else {
|
|
279
|
+
// root entity
|
|
280
|
+
this.#state.populate.push(item);
|
|
284
281
|
}
|
|
285
|
-
this.
|
|
282
|
+
this.#state.joinedProps.set(alias, item);
|
|
286
283
|
this.addSelect(this.getFieldsForJoinedLoad(prop, alias, fields));
|
|
287
284
|
return this;
|
|
288
285
|
}
|
|
@@ -303,12 +300,12 @@ export class QueryBuilder {
|
|
|
303
300
|
getFieldsForJoinedLoad(prop, alias, explicitFields) {
|
|
304
301
|
const fields = [];
|
|
305
302
|
const populate = [];
|
|
306
|
-
const joinKey = Object.keys(this.
|
|
303
|
+
const joinKey = Object.keys(this.#state.joins).find(join => join.endsWith(`#${alias}`));
|
|
307
304
|
const targetMeta = prop.targetMeta;
|
|
308
|
-
const schema = this.
|
|
305
|
+
const schema = this.#state.schema ?? (targetMeta.schema !== '*' ? targetMeta.schema : undefined);
|
|
309
306
|
if (joinKey) {
|
|
310
|
-
const path = this.
|
|
311
|
-
let children = this.
|
|
307
|
+
const path = this.#state.joins[joinKey].path.split('.').slice(1);
|
|
308
|
+
let children = this.#state.populate;
|
|
312
309
|
for (let i = 0; i < path.length; i++) {
|
|
313
310
|
const child = children.filter(hint => {
|
|
314
311
|
const [propName] = hint.field.split(':', 2);
|
|
@@ -358,13 +355,13 @@ export class QueryBuilder {
|
|
|
358
355
|
* @internal
|
|
359
356
|
*/
|
|
360
357
|
scheduleFilterCheck(path) {
|
|
361
|
-
this.autoJoinedPaths.push(path);
|
|
358
|
+
this.#state.autoJoinedPaths.push(path);
|
|
362
359
|
}
|
|
363
360
|
/**
|
|
364
361
|
* @internal
|
|
365
362
|
*/
|
|
366
363
|
async applyJoinedFilters(em, filterOptions) {
|
|
367
|
-
for (const path of this.autoJoinedPaths) {
|
|
364
|
+
for (const path of this.#state.autoJoinedPaths) {
|
|
368
365
|
const join = this.getJoinForPath(path);
|
|
369
366
|
if (join.type === JoinType.pivotJoin) {
|
|
370
367
|
continue;
|
|
@@ -382,7 +379,8 @@ export class QueryBuilder {
|
|
|
382
379
|
if (Utils.hasObjectKeys(cond) || RawQueryFragment.hasObjectFragments(cond)) {
|
|
383
380
|
// remove nested filters, we only care about scalars here, nesting would require another join branch
|
|
384
381
|
for (const key of Object.keys(cond)) {
|
|
385
|
-
if (Utils.isPlainObject(cond[key]) &&
|
|
382
|
+
if (Utils.isPlainObject(cond[key]) &&
|
|
383
|
+
Object.keys(cond[key]).every(k => !(Utils.isOperator(k) && !['$some', '$none', '$every', '$size'].includes(k)))) {
|
|
386
384
|
delete cond[key];
|
|
387
385
|
}
|
|
388
386
|
}
|
|
@@ -399,10 +397,10 @@ export class QueryBuilder {
|
|
|
399
397
|
withSubQuery(subQuery, alias) {
|
|
400
398
|
this.ensureNotFinalized();
|
|
401
399
|
if (isRaw(subQuery)) {
|
|
402
|
-
this.subQueries[alias] = this.platform.formatQuery(subQuery.sql, subQuery.params);
|
|
400
|
+
this.#state.subQueries[alias] = this.platform.formatQuery(subQuery.sql, subQuery.params);
|
|
403
401
|
}
|
|
404
402
|
else {
|
|
405
|
-
this.subQueries[alias] = subQuery.toString();
|
|
403
|
+
this.#state.subQueries[alias] = subQuery.toString();
|
|
406
404
|
}
|
|
407
405
|
return this;
|
|
408
406
|
}
|
|
@@ -426,31 +424,32 @@ export class QueryBuilder {
|
|
|
426
424
|
platform: this.platform,
|
|
427
425
|
aliasMap: this.getAliasMap(),
|
|
428
426
|
aliased: [QueryType.SELECT, QueryType.COUNT].includes(this.type),
|
|
429
|
-
convertCustomTypes: this.flags.has(QueryFlag.CONVERT_CUSTOM_TYPES),
|
|
427
|
+
convertCustomTypes: this.#state.flags.has(QueryFlag.CONVERT_CUSTOM_TYPES),
|
|
430
428
|
});
|
|
431
429
|
}
|
|
432
430
|
const op = operator || params;
|
|
433
|
-
const topLevel = !op || !(Utils.hasObjectKeys(this.
|
|
431
|
+
const topLevel = !op || !(Utils.hasObjectKeys(this.#state.cond) || RawQueryFragment.hasObjectFragments(this.#state.cond));
|
|
434
432
|
const criteriaNode = CriteriaNodeFactory.createNode(this.metadata, this.mainAlias.entityName, processedCond);
|
|
435
|
-
const ignoreBranching = this.
|
|
436
|
-
if ([QueryType.UPDATE, QueryType.DELETE].includes(this.type) &&
|
|
433
|
+
const ignoreBranching = this.#state.resolvedPopulateWhere === 'infer';
|
|
434
|
+
if ([QueryType.UPDATE, QueryType.DELETE].includes(this.type) &&
|
|
435
|
+
criteriaNode.willAutoJoin(this, undefined, { ignoreBranching })) {
|
|
437
436
|
// use sub-query to support joining
|
|
438
437
|
this.setFlag(this.type === QueryType.UPDATE ? QueryFlag.UPDATE_SUB_QUERY : QueryFlag.DELETE_SUB_QUERY);
|
|
439
438
|
this.select(this.mainAlias.meta.primaryKeys, true);
|
|
440
439
|
}
|
|
441
440
|
if (topLevel) {
|
|
442
|
-
this.
|
|
441
|
+
this.#state.cond = criteriaNode.process(this, { ignoreBranching });
|
|
443
442
|
}
|
|
444
|
-
else if (Array.isArray(this.
|
|
445
|
-
this.
|
|
443
|
+
else if (Array.isArray(this.#state.cond[op])) {
|
|
444
|
+
this.#state.cond[op].push(criteriaNode.process(this, { ignoreBranching }));
|
|
446
445
|
}
|
|
447
446
|
else {
|
|
448
|
-
const cond1 = [this.
|
|
449
|
-
this.
|
|
447
|
+
const cond1 = [this.#state.cond, criteriaNode.process(this, { ignoreBranching })];
|
|
448
|
+
this.#state.cond = { [op]: cond1 };
|
|
450
449
|
}
|
|
451
|
-
if (this.
|
|
452
|
-
this.
|
|
453
|
-
this.
|
|
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 = {};
|
|
454
453
|
}
|
|
455
454
|
return this;
|
|
456
455
|
}
|
|
@@ -469,7 +468,7 @@ export class QueryBuilder {
|
|
|
469
468
|
processOrderBy(orderBy, reset = true) {
|
|
470
469
|
this.ensureNotFinalized();
|
|
471
470
|
if (reset) {
|
|
472
|
-
this.
|
|
471
|
+
this.#state.orderBy = [];
|
|
473
472
|
}
|
|
474
473
|
const selectAliases = this.getSelectAliases();
|
|
475
474
|
Utils.asArray(orderBy).forEach(orig => {
|
|
@@ -494,7 +493,7 @@ export class QueryBuilder {
|
|
|
494
493
|
convertCustomTypes: false,
|
|
495
494
|
type: 'orderBy',
|
|
496
495
|
});
|
|
497
|
-
this.
|
|
496
|
+
this.#state.orderBy.push(CriteriaNodeFactory.createNode(this.metadata, this.mainAlias.entityName, processed).process(this, {
|
|
498
497
|
matchPopulateJoins: true,
|
|
499
498
|
type: 'orderBy',
|
|
500
499
|
}));
|
|
@@ -504,9 +503,9 @@ export class QueryBuilder {
|
|
|
504
503
|
/** Collect custom aliases from select fields (stored as 'resolved as alias' strings by select()). */
|
|
505
504
|
getSelectAliases() {
|
|
506
505
|
const aliases = new Set();
|
|
507
|
-
for (const field of this.
|
|
506
|
+
for (const field of this.#state.fields ?? []) {
|
|
508
507
|
if (typeof field === 'string') {
|
|
509
|
-
const m =
|
|
508
|
+
const m = FIELD_ALIAS_RE.exec(field);
|
|
510
509
|
if (m) {
|
|
511
510
|
aliases.add(m[2]);
|
|
512
511
|
}
|
|
@@ -516,7 +515,7 @@ export class QueryBuilder {
|
|
|
516
515
|
}
|
|
517
516
|
groupBy(fields) {
|
|
518
517
|
this.ensureNotFinalized();
|
|
519
|
-
this.
|
|
518
|
+
this.#state.groupBy = Utils.asArray(fields).flatMap(f => {
|
|
520
519
|
if (typeof f !== 'string') {
|
|
521
520
|
// Normalize sql.ref('prop') to string for proper formula resolution
|
|
522
521
|
if (isRaw(f) && f.sql === '??' && f.params.length === 1) {
|
|
@@ -544,12 +543,12 @@ export class QueryBuilder {
|
|
|
544
543
|
cond = { [raw(`(${cond})`, params)]: [] };
|
|
545
544
|
}
|
|
546
545
|
const processed = CriteriaNodeFactory.createNode(this.metadata, this.mainAlias.entityName, cond, undefined, undefined, false).process(this, { type: 'having' });
|
|
547
|
-
if (!this.
|
|
548
|
-
this.
|
|
546
|
+
if (!this.#state.having || !operator) {
|
|
547
|
+
this.#state.having = processed;
|
|
549
548
|
}
|
|
550
549
|
else {
|
|
551
|
-
const cond1 = [this.
|
|
552
|
-
this.
|
|
550
|
+
const cond1 = [this.#state.having, processed];
|
|
551
|
+
this.#state.having = { [operator]: cond1 };
|
|
553
552
|
}
|
|
554
553
|
return this;
|
|
555
554
|
}
|
|
@@ -562,8 +561,8 @@ export class QueryBuilder {
|
|
|
562
561
|
onConflict(fields = []) {
|
|
563
562
|
const meta = this.mainAlias.meta;
|
|
564
563
|
this.ensureNotFinalized();
|
|
565
|
-
this.
|
|
566
|
-
this.
|
|
564
|
+
this.#state.onConflict ??= [];
|
|
565
|
+
this.#state.onConflict.push({
|
|
567
566
|
fields: isRaw(fields)
|
|
568
567
|
? fields
|
|
569
568
|
: Utils.asArray(fields).flatMap(f => {
|
|
@@ -575,24 +574,24 @@ export class QueryBuilder {
|
|
|
575
574
|
return this;
|
|
576
575
|
}
|
|
577
576
|
ignore() {
|
|
578
|
-
if (!this.
|
|
577
|
+
if (!this.#state.onConflict) {
|
|
579
578
|
throw new Error('You need to call `qb.onConflict()` first to use `qb.ignore()`');
|
|
580
579
|
}
|
|
581
|
-
this.
|
|
580
|
+
this.#state.onConflict[this.#state.onConflict.length - 1].ignore = true;
|
|
582
581
|
return this;
|
|
583
582
|
}
|
|
584
583
|
merge(data) {
|
|
585
|
-
if (!this.
|
|
584
|
+
if (!this.#state.onConflict) {
|
|
586
585
|
throw new Error('You need to call `qb.onConflict()` first to use `qb.merge()`');
|
|
587
586
|
}
|
|
588
587
|
if (Array.isArray(data) && data.length === 0) {
|
|
589
588
|
return this.ignore();
|
|
590
589
|
}
|
|
591
|
-
this.
|
|
590
|
+
this.#state.onConflict[this.#state.onConflict.length - 1].merge = data;
|
|
592
591
|
return this;
|
|
593
592
|
}
|
|
594
593
|
returning(fields) {
|
|
595
|
-
this.
|
|
594
|
+
this.#state.returning = Utils.asArray(fields);
|
|
596
595
|
return this;
|
|
597
596
|
}
|
|
598
597
|
/**
|
|
@@ -600,9 +599,9 @@ export class QueryBuilder {
|
|
|
600
599
|
*/
|
|
601
600
|
populate(populate, populateWhere, populateFilter) {
|
|
602
601
|
this.ensureNotFinalized();
|
|
603
|
-
this.
|
|
604
|
-
this.
|
|
605
|
-
this.
|
|
602
|
+
this.#state.populate = populate;
|
|
603
|
+
this.#state.populateWhere = populateWhere;
|
|
604
|
+
this.#state.populateFilter = populateFilter;
|
|
606
605
|
return this;
|
|
607
606
|
}
|
|
608
607
|
/**
|
|
@@ -616,7 +615,7 @@ export class QueryBuilder {
|
|
|
616
615
|
*/
|
|
617
616
|
limit(limit, offset = 0) {
|
|
618
617
|
this.ensureNotFinalized();
|
|
619
|
-
this.
|
|
618
|
+
this.#state.limit = limit;
|
|
620
619
|
if (offset) {
|
|
621
620
|
this.offset(offset);
|
|
622
621
|
}
|
|
@@ -632,12 +631,12 @@ export class QueryBuilder {
|
|
|
632
631
|
*/
|
|
633
632
|
offset(offset) {
|
|
634
633
|
this.ensureNotFinalized();
|
|
635
|
-
this.
|
|
634
|
+
this.#state.offset = offset;
|
|
636
635
|
return this;
|
|
637
636
|
}
|
|
638
637
|
withSchema(schema) {
|
|
639
638
|
this.ensureNotFinalized();
|
|
640
|
-
this.
|
|
639
|
+
this.#state.schema = schema;
|
|
641
640
|
return this;
|
|
642
641
|
}
|
|
643
642
|
setLockMode(mode, tables) {
|
|
@@ -645,31 +644,31 @@ export class QueryBuilder {
|
|
|
645
644
|
if (mode != null && ![LockMode.OPTIMISTIC, LockMode.NONE].includes(mode) && !this.context) {
|
|
646
645
|
throw ValidationError.transactionRequired();
|
|
647
646
|
}
|
|
648
|
-
this.lockMode = mode;
|
|
649
|
-
this.lockTables = tables;
|
|
647
|
+
this.#state.lockMode = mode;
|
|
648
|
+
this.#state.lockTables = tables;
|
|
650
649
|
return this;
|
|
651
650
|
}
|
|
652
651
|
setFlushMode(flushMode) {
|
|
653
652
|
this.ensureNotFinalized();
|
|
654
|
-
this.flushMode = flushMode;
|
|
653
|
+
this.#state.flushMode = flushMode;
|
|
655
654
|
return this;
|
|
656
655
|
}
|
|
657
656
|
setFlag(flag) {
|
|
658
657
|
this.ensureNotFinalized();
|
|
659
|
-
this.flags.add(flag);
|
|
658
|
+
this.#state.flags.add(flag);
|
|
660
659
|
return this;
|
|
661
660
|
}
|
|
662
661
|
unsetFlag(flag) {
|
|
663
662
|
this.ensureNotFinalized();
|
|
664
|
-
this.flags.delete(flag);
|
|
663
|
+
this.#state.flags.delete(flag);
|
|
665
664
|
return this;
|
|
666
665
|
}
|
|
667
666
|
hasFlag(flag) {
|
|
668
|
-
return this.flags.has(flag);
|
|
667
|
+
return this.#state.flags.has(flag);
|
|
669
668
|
}
|
|
670
669
|
cache(config = true) {
|
|
671
670
|
this.ensureNotFinalized();
|
|
672
|
-
this.
|
|
671
|
+
this.#state.cache = config;
|
|
673
672
|
return this;
|
|
674
673
|
}
|
|
675
674
|
/**
|
|
@@ -677,7 +676,7 @@ export class QueryBuilder {
|
|
|
677
676
|
*/
|
|
678
677
|
indexHint(sql) {
|
|
679
678
|
this.ensureNotFinalized();
|
|
680
|
-
this.
|
|
679
|
+
this.#state.indexHint = sql;
|
|
681
680
|
return this;
|
|
682
681
|
}
|
|
683
682
|
/**
|
|
@@ -685,7 +684,7 @@ export class QueryBuilder {
|
|
|
685
684
|
*/
|
|
686
685
|
collation(collation) {
|
|
687
686
|
this.ensureNotFinalized();
|
|
688
|
-
this.
|
|
687
|
+
this.#state.collation = collation;
|
|
689
688
|
return this;
|
|
690
689
|
}
|
|
691
690
|
/**
|
|
@@ -693,7 +692,7 @@ export class QueryBuilder {
|
|
|
693
692
|
*/
|
|
694
693
|
comment(comment) {
|
|
695
694
|
this.ensureNotFinalized();
|
|
696
|
-
this.
|
|
695
|
+
this.#state.comments.push(...Utils.asArray(comment));
|
|
697
696
|
return this;
|
|
698
697
|
}
|
|
699
698
|
/**
|
|
@@ -703,52 +702,110 @@ export class QueryBuilder {
|
|
|
703
702
|
*/
|
|
704
703
|
hintComment(comment) {
|
|
705
704
|
this.ensureNotFinalized();
|
|
706
|
-
this.
|
|
705
|
+
this.#state.hintComments.push(...Utils.asArray(comment));
|
|
707
706
|
return this;
|
|
708
707
|
}
|
|
709
708
|
from(target, aliasName) {
|
|
710
709
|
this.ensureNotFinalized();
|
|
711
|
-
if (target instanceof
|
|
710
|
+
if (target instanceof _a) {
|
|
712
711
|
this.fromSubQuery(target, aliasName);
|
|
713
712
|
}
|
|
713
|
+
else if (typeof target === 'string' && !this.metadata.find(target)) {
|
|
714
|
+
this.fromRawTable(target, aliasName);
|
|
715
|
+
}
|
|
714
716
|
else {
|
|
715
|
-
if (aliasName && this.
|
|
716
|
-
throw new Error(`Cannot override the alias to '${aliasName}' since a query already contains references to '${this.
|
|
717
|
+
if (aliasName && this.#state.mainAlias && Utils.className(target) !== this.#state.mainAlias.aliasName) {
|
|
718
|
+
throw new Error(`Cannot override the alias to '${aliasName}' since a query already contains references to '${this.#state.mainAlias.aliasName}'`);
|
|
717
719
|
}
|
|
718
720
|
this.fromEntityName(target, aliasName);
|
|
719
721
|
}
|
|
720
722
|
return this;
|
|
721
723
|
}
|
|
722
724
|
getNativeQuery(processVirtualEntity = true) {
|
|
723
|
-
if (this.
|
|
724
|
-
|
|
725
|
+
if (this.#state.unionQuery) {
|
|
726
|
+
if (!this.#query?.qb) {
|
|
727
|
+
this.#query = {};
|
|
728
|
+
const nqb = this.platform.createNativeQueryBuilder();
|
|
729
|
+
nqb.select('*');
|
|
730
|
+
nqb.from(raw(`(${this.#state.unionQuery.sql})`, this.#state.unionQuery.params));
|
|
731
|
+
this.#query.qb = nqb;
|
|
732
|
+
}
|
|
733
|
+
return this.#query.qb;
|
|
725
734
|
}
|
|
726
|
-
this
|
|
735
|
+
if (this.#query?.qb) {
|
|
736
|
+
return this.#query.qb;
|
|
737
|
+
}
|
|
738
|
+
this.#query = {};
|
|
727
739
|
this.finalize();
|
|
728
740
|
const qb = this.getQueryBase(processVirtualEntity);
|
|
741
|
+
for (const cte of this.#state.ctes) {
|
|
742
|
+
const query = cte.query;
|
|
743
|
+
const opts = { columns: cte.columns, materialized: cte.materialized };
|
|
744
|
+
if (cte.recursive) {
|
|
745
|
+
qb.withRecursive(cte.name, query, opts);
|
|
746
|
+
}
|
|
747
|
+
else {
|
|
748
|
+
qb.with(cte.name, query, opts);
|
|
749
|
+
}
|
|
750
|
+
}
|
|
729
751
|
const schema = this.getSchema(this.mainAlias);
|
|
730
752
|
const isNotEmptyObject = (obj) => Utils.hasObjectKeys(obj) || RawQueryFragment.hasObjectFragments(obj);
|
|
731
|
-
Utils.runIfNotEmpty(() => this.helper.appendQueryCondition(this.type, this.
|
|
732
|
-
Utils.runIfNotEmpty(() => qb.groupBy(this.prepareFields(this.
|
|
733
|
-
Utils.runIfNotEmpty(() => this.helper.appendQueryCondition(this.type, this.
|
|
753
|
+
Utils.runIfNotEmpty(() => this.helper.appendQueryCondition(this.type, this.#state.cond, qb), this.#state.cond && !this.#state.onConflict);
|
|
754
|
+
Utils.runIfNotEmpty(() => qb.groupBy(this.prepareFields(this.#state.groupBy, 'groupBy', schema)), isNotEmptyObject(this.#state.groupBy));
|
|
755
|
+
Utils.runIfNotEmpty(() => this.helper.appendQueryCondition(this.type, this.#state.having, qb, undefined, 'having'), isNotEmptyObject(this.#state.having));
|
|
734
756
|
Utils.runIfNotEmpty(() => {
|
|
735
|
-
const queryOrder = this.helper.getQueryOrder(this.type, this.
|
|
757
|
+
const queryOrder = this.helper.getQueryOrder(this.type, this.#state.orderBy, this.#state.populateMap, this.#state.collation);
|
|
736
758
|
if (queryOrder.length > 0) {
|
|
737
759
|
const sql = Utils.unique(queryOrder).join(', ');
|
|
738
760
|
qb.orderBy(sql);
|
|
739
761
|
return;
|
|
740
762
|
}
|
|
741
|
-
}, isNotEmptyObject(this.
|
|
742
|
-
Utils.runIfNotEmpty(() => qb.limit(this.
|
|
743
|
-
Utils.runIfNotEmpty(() => qb.offset(this.
|
|
744
|
-
Utils.runIfNotEmpty(() => qb.comment(this.
|
|
745
|
-
Utils.runIfNotEmpty(() => qb.hintComment(this.
|
|
746
|
-
Utils.runIfNotEmpty(() => this.helper.appendOnConflictClause(QueryType.UPSERT, this.
|
|
747
|
-
if (this.lockMode) {
|
|
748
|
-
this.helper.getLockSQL(qb, this.lockMode, this.lockTables, this.
|
|
749
|
-
}
|
|
750
|
-
this.
|
|
751
|
-
return this.
|
|
763
|
+
}, isNotEmptyObject(this.#state.orderBy));
|
|
764
|
+
Utils.runIfNotEmpty(() => qb.limit(this.#state.limit), this.#state.limit != null);
|
|
765
|
+
Utils.runIfNotEmpty(() => qb.offset(this.#state.offset), this.#state.offset);
|
|
766
|
+
Utils.runIfNotEmpty(() => qb.comment(this.#state.comments), this.#state.comments);
|
|
767
|
+
Utils.runIfNotEmpty(() => qb.hintComment(this.#state.hintComments), this.#state.hintComments);
|
|
768
|
+
Utils.runIfNotEmpty(() => this.helper.appendOnConflictClause(QueryType.UPSERT, this.#state.onConflict, qb), this.#state.onConflict);
|
|
769
|
+
if (this.#state.lockMode) {
|
|
770
|
+
this.helper.getLockSQL(qb, this.#state.lockMode, this.#state.lockTables, this.#state.joins);
|
|
771
|
+
}
|
|
772
|
+
this.processReturningStatement(qb, this.mainAlias.meta, this.#state.data, this.#state.returning);
|
|
773
|
+
return (this.#query.qb = qb);
|
|
774
|
+
}
|
|
775
|
+
processReturningStatement(qb, meta, data, returning) {
|
|
776
|
+
const usesReturningStatement = this.platform.usesReturningStatement() || this.platform.usesOutputStatement();
|
|
777
|
+
if (!meta || !data || !usesReturningStatement) {
|
|
778
|
+
return;
|
|
779
|
+
}
|
|
780
|
+
// always respect explicit returning hint
|
|
781
|
+
if (returning && returning.length > 0) {
|
|
782
|
+
qb.returning(returning.map(field => this.helper.mapper(field, this.type)));
|
|
783
|
+
return;
|
|
784
|
+
}
|
|
785
|
+
if (this.type === QueryType.INSERT) {
|
|
786
|
+
const returningProps = meta.hydrateProps
|
|
787
|
+
.filter(prop => prop.returning || (prop.persist !== false && ((prop.primary && prop.autoincrement) || prop.defaultRaw)))
|
|
788
|
+
.filter(prop => !(prop.name in data));
|
|
789
|
+
if (returningProps.length > 0) {
|
|
790
|
+
qb.returning(Utils.flatten(returningProps.map(prop => prop.fieldNames)));
|
|
791
|
+
}
|
|
792
|
+
return;
|
|
793
|
+
}
|
|
794
|
+
if (this.type === QueryType.UPDATE) {
|
|
795
|
+
const returningProps = meta.hydrateProps.filter(prop => prop.fieldNames && isRaw(data[prop.fieldNames[0]]));
|
|
796
|
+
if (returningProps.length > 0) {
|
|
797
|
+
qb.returning(returningProps.flatMap((prop) => {
|
|
798
|
+
if (prop.hasConvertToJSValueSQL) {
|
|
799
|
+
const aliased = this.platform.quoteIdentifier(prop.fieldNames[0]);
|
|
800
|
+
const sql = prop.customType.convertToJSValueSQL(aliased, this.platform) +
|
|
801
|
+
' as ' +
|
|
802
|
+
this.platform.quoteIdentifier(prop.fieldNames[0]);
|
|
803
|
+
return [raw(sql)];
|
|
804
|
+
}
|
|
805
|
+
return prop.fieldNames;
|
|
806
|
+
}));
|
|
807
|
+
}
|
|
808
|
+
}
|
|
752
809
|
}
|
|
753
810
|
/**
|
|
754
811
|
* Returns the query with parameters as wildcards.
|
|
@@ -764,13 +821,16 @@ export class QueryBuilder {
|
|
|
764
821
|
return raw(sql, params);
|
|
765
822
|
}
|
|
766
823
|
toQuery() {
|
|
767
|
-
if (this.
|
|
768
|
-
return
|
|
824
|
+
if (this.#state.unionQuery) {
|
|
825
|
+
return this.#state.unionQuery;
|
|
826
|
+
}
|
|
827
|
+
if (this.#query?.sql) {
|
|
828
|
+
return { sql: this.#query.sql, params: this.#query.params };
|
|
769
829
|
}
|
|
770
830
|
const query = this.getNativeQuery().compile();
|
|
771
|
-
this.
|
|
772
|
-
this.
|
|
773
|
-
return { sql: this.
|
|
831
|
+
this.#query.sql = query.sql;
|
|
832
|
+
this.#query.params = query.params;
|
|
833
|
+
return { sql: this.#query.sql, params: this.#query.params };
|
|
774
834
|
}
|
|
775
835
|
/**
|
|
776
836
|
* Returns the list of all parameters for this query.
|
|
@@ -802,7 +862,7 @@ export class QueryBuilder {
|
|
|
802
862
|
* @internal
|
|
803
863
|
*/
|
|
804
864
|
getJoinForPath(path, options) {
|
|
805
|
-
const joins = Object.values(this.
|
|
865
|
+
const joins = Object.values(this.#state.joins);
|
|
806
866
|
if (joins.length === 0) {
|
|
807
867
|
return undefined;
|
|
808
868
|
}
|
|
@@ -834,7 +894,7 @@ export class QueryBuilder {
|
|
|
834
894
|
*/
|
|
835
895
|
getNextAlias(entityName = 'e') {
|
|
836
896
|
entityName = Utils.className(entityName);
|
|
837
|
-
return this.driver.config.getNamingStrategy().aliasName(entityName, this.aliasCounter++);
|
|
897
|
+
return this.driver.config.getNamingStrategy().aliasName(entityName, this.#state.aliasCounter++);
|
|
838
898
|
}
|
|
839
899
|
/**
|
|
840
900
|
* Registers a join for a specific polymorphic target type.
|
|
@@ -847,15 +907,15 @@ export class QueryBuilder {
|
|
|
847
907
|
const referencedColumnNames = targetMeta.getPrimaryProps().flatMap(pk => pk.fieldNames);
|
|
848
908
|
const targetProp = { ...prop, targetMeta, referencedColumnNames };
|
|
849
909
|
const aliasedName = `${ownerAlias}.${prop.name}[${targetMeta.className}]#${alias}`;
|
|
850
|
-
this.
|
|
851
|
-
this.
|
|
910
|
+
this.#state.joins[aliasedName] = this.helper.joinManyToOneReference(targetProp, ownerAlias, alias, type, {}, schema);
|
|
911
|
+
this.#state.joins[aliasedName].path = path;
|
|
852
912
|
this.createAlias(targetMeta.class, alias);
|
|
853
913
|
}
|
|
854
914
|
/**
|
|
855
915
|
* @internal
|
|
856
916
|
*/
|
|
857
917
|
getAliasMap() {
|
|
858
|
-
return Object.fromEntries(Object.entries(this.
|
|
918
|
+
return Object.fromEntries(Object.entries(this.#state.aliases).map(([key, value]) => [key, value.entityName]));
|
|
859
919
|
}
|
|
860
920
|
/**
|
|
861
921
|
* Executes this QB and returns the raw results, mapped to the property names (unless disabled via last parameter).
|
|
@@ -870,11 +930,16 @@ export class QueryBuilder {
|
|
|
870
930
|
if (!this.connectionType && (isRunType || this.context)) {
|
|
871
931
|
this.connectionType = 'write';
|
|
872
932
|
}
|
|
873
|
-
if (!this.finalized && method === 'get' && this.type === QueryType.SELECT) {
|
|
933
|
+
if (!this.#state.finalized && method === 'get' && this.type === QueryType.SELECT) {
|
|
874
934
|
this.limit(1);
|
|
875
935
|
}
|
|
876
936
|
const query = this.toQuery();
|
|
877
|
-
const cached = await this.em?.tryCache(this.mainAlias.entityName, this.
|
|
937
|
+
const cached = await this.em?.tryCache(this.mainAlias.entityName, this.#state.cache, [
|
|
938
|
+
'qb.execute',
|
|
939
|
+
query.sql,
|
|
940
|
+
query.params,
|
|
941
|
+
method,
|
|
942
|
+
]);
|
|
878
943
|
if (cached?.data !== undefined) {
|
|
879
944
|
return cached.data;
|
|
880
945
|
}
|
|
@@ -882,17 +947,17 @@ export class QueryBuilder {
|
|
|
882
947
|
const res = await this.getConnection().execute(query.sql, query.params, method, this.context, loggerContext);
|
|
883
948
|
const meta = this.mainAlias.meta;
|
|
884
949
|
if (!options.mapResults || !meta) {
|
|
885
|
-
await this.em?.storeCache(this.
|
|
950
|
+
await this.em?.storeCache(this.#state.cache, cached, res);
|
|
886
951
|
return res;
|
|
887
952
|
}
|
|
888
953
|
if (method === 'run') {
|
|
889
954
|
return res;
|
|
890
955
|
}
|
|
891
|
-
const joinedProps = this.driver.joinedProps(meta, this.
|
|
956
|
+
const joinedProps = this.driver.joinedProps(meta, this.#state.populate);
|
|
892
957
|
let mapped;
|
|
893
958
|
if (Array.isArray(res)) {
|
|
894
959
|
const map = {};
|
|
895
|
-
mapped = res.map(r => this.driver.mapResult(r, meta, this.
|
|
960
|
+
mapped = res.map(r => this.driver.mapResult(r, meta, this.#state.populate, this, map));
|
|
896
961
|
if (options.mergeResults && joinedProps.length > 0) {
|
|
897
962
|
mapped = this.driver.mergeJoinedResult(mapped, this.mainAlias.meta, joinedProps);
|
|
898
963
|
}
|
|
@@ -901,10 +966,10 @@ export class QueryBuilder {
|
|
|
901
966
|
mapped = [this.driver.mapResult(res, meta, joinedProps, this)];
|
|
902
967
|
}
|
|
903
968
|
if (method === 'get') {
|
|
904
|
-
await this.em?.storeCache(this.
|
|
969
|
+
await this.em?.storeCache(this.#state.cache, cached, mapped[0]);
|
|
905
970
|
return mapped[0];
|
|
906
971
|
}
|
|
907
|
-
await this.em?.storeCache(this.
|
|
972
|
+
await this.em?.storeCache(this.#state.cache, cached, mapped);
|
|
908
973
|
return mapped;
|
|
909
974
|
}
|
|
910
975
|
getConnection() {
|
|
@@ -940,13 +1005,13 @@ export class QueryBuilder {
|
|
|
940
1005
|
yield* res;
|
|
941
1006
|
return;
|
|
942
1007
|
}
|
|
943
|
-
const joinedProps = this.driver.joinedProps(meta, this.
|
|
1008
|
+
const joinedProps = this.driver.joinedProps(meta, this.#state.populate);
|
|
944
1009
|
const stack = [];
|
|
945
1010
|
const hash = (data) => {
|
|
946
1011
|
return Utils.getPrimaryKeyHash(meta.primaryKeys.map(pk => data[pk]));
|
|
947
1012
|
};
|
|
948
1013
|
for await (const row of res) {
|
|
949
|
-
const mapped = this.driver.mapResult(row, meta, this.
|
|
1014
|
+
const mapped = this.driver.mapResult(row, meta, this.#state.populate, this);
|
|
950
1015
|
if (!options.mergeResults || joinedProps.length === 0) {
|
|
951
1016
|
yield this.mapResult(mapped, options.mapResults);
|
|
952
1017
|
continue;
|
|
@@ -975,7 +1040,7 @@ export class QueryBuilder {
|
|
|
975
1040
|
* Executes the query, returning array of results mapped to entity instances.
|
|
976
1041
|
*/
|
|
977
1042
|
async getResultList(limit) {
|
|
978
|
-
await this.em.tryFlush(this.mainAlias.entityName, { flushMode: this.flushMode });
|
|
1043
|
+
await this.em.tryFlush(this.mainAlias.entityName, { flushMode: this.#state.flushMode });
|
|
979
1044
|
const res = await this.execute('all', true);
|
|
980
1045
|
return this.mapResults(res, limit);
|
|
981
1046
|
}
|
|
@@ -997,15 +1062,15 @@ export class QueryBuilder {
|
|
|
997
1062
|
if (!map) {
|
|
998
1063
|
return row;
|
|
999
1064
|
}
|
|
1000
|
-
const entity = this.em.map(this.mainAlias.entityName, row, { schema: this.
|
|
1001
|
-
this.propagatePopulateHint(entity, this.
|
|
1065
|
+
const entity = this.em.map(this.mainAlias.entityName, row, { schema: this.#state.schema });
|
|
1066
|
+
this.propagatePopulateHint(entity, this.#state.populate);
|
|
1002
1067
|
return entity;
|
|
1003
1068
|
}
|
|
1004
1069
|
mapResults(res, limit) {
|
|
1005
1070
|
const entities = [];
|
|
1006
1071
|
for (const row of res) {
|
|
1007
1072
|
const entity = this.mapResult(row);
|
|
1008
|
-
this.propagatePopulateHint(entity, this.
|
|
1073
|
+
this.propagatePopulateHint(entity, this.#state.populate);
|
|
1009
1074
|
entities.push(entity);
|
|
1010
1075
|
if (limit != null && --limit === 0) {
|
|
1011
1076
|
break;
|
|
@@ -1017,7 +1082,7 @@ export class QueryBuilder {
|
|
|
1017
1082
|
* Executes the query, returning the first result or null
|
|
1018
1083
|
*/
|
|
1019
1084
|
async getSingleResult() {
|
|
1020
|
-
if (!this.finalized) {
|
|
1085
|
+
if (!this.#state.finalized) {
|
|
1021
1086
|
this.limit(1);
|
|
1022
1087
|
}
|
|
1023
1088
|
const [res] = await this.getResultList(1);
|
|
@@ -1029,9 +1094,12 @@ export class QueryBuilder {
|
|
|
1029
1094
|
res = await this.execute('get', false);
|
|
1030
1095
|
}
|
|
1031
1096
|
else {
|
|
1032
|
-
const qb = (this.
|
|
1097
|
+
const qb = (this.#state.type === undefined ? this : this.clone());
|
|
1033
1098
|
qb.processPopulateHint(); // needs to happen sooner so `qb.hasToManyJoins()` reports correctly
|
|
1034
|
-
qb.count(field, distinct ?? qb.hasToManyJoins())
|
|
1099
|
+
qb.count(field, distinct ?? qb.hasToManyJoins())
|
|
1100
|
+
.limit(undefined)
|
|
1101
|
+
.offset(undefined)
|
|
1102
|
+
.orderBy([]);
|
|
1035
1103
|
res = await qb.execute('get', false);
|
|
1036
1104
|
}
|
|
1037
1105
|
return res ? +res.count : 0;
|
|
@@ -1059,28 +1127,97 @@ export class QueryBuilder {
|
|
|
1059
1127
|
Object.defineProperty(qb, '__as', { enumerable: false, value: finalAlias });
|
|
1060
1128
|
return qb;
|
|
1061
1129
|
}
|
|
1130
|
+
/**
|
|
1131
|
+
* Combines the current query with one or more other queries using `UNION ALL`.
|
|
1132
|
+
* All queries must select the same columns. Returns a `QueryBuilder` that
|
|
1133
|
+
* can be used with `$in`, passed to `qb.from()`, or converted via `.getQuery()`,
|
|
1134
|
+
* `.getParams()`, `.toQuery()`, `.toRaw()`, etc.
|
|
1135
|
+
*
|
|
1136
|
+
* ```ts
|
|
1137
|
+
* const qb1 = em.createQueryBuilder(Employee).select('id').where(condition1);
|
|
1138
|
+
* const qb2 = em.createQueryBuilder(Employee).select('id').where(condition2);
|
|
1139
|
+
* const qb3 = em.createQueryBuilder(Employee).select('id').where(condition3);
|
|
1140
|
+
* const subquery = qb1.unionAll(qb2, qb3);
|
|
1141
|
+
*
|
|
1142
|
+
* const results = await em.find(Employee, { id: { $in: subquery } });
|
|
1143
|
+
* ```
|
|
1144
|
+
*/
|
|
1145
|
+
unionAll(...others) {
|
|
1146
|
+
return this.buildUnionQuery('union all', others);
|
|
1147
|
+
}
|
|
1148
|
+
/**
|
|
1149
|
+
* Combines the current query with one or more other queries using `UNION` (with deduplication).
|
|
1150
|
+
* All queries must select the same columns. Returns a `QueryBuilder` that
|
|
1151
|
+
* can be used with `$in`, passed to `qb.from()`, or converted via `.getQuery()`,
|
|
1152
|
+
* `.getParams()`, `.toQuery()`, `.toRaw()`, etc.
|
|
1153
|
+
*
|
|
1154
|
+
* ```ts
|
|
1155
|
+
* const qb1 = em.createQueryBuilder(Employee).select('id').where(condition1);
|
|
1156
|
+
* const qb2 = em.createQueryBuilder(Employee).select('id').where(condition2);
|
|
1157
|
+
* const subquery = qb1.union(qb2);
|
|
1158
|
+
*
|
|
1159
|
+
* const results = await em.find(Employee, { id: { $in: subquery } });
|
|
1160
|
+
* ```
|
|
1161
|
+
*/
|
|
1162
|
+
union(...others) {
|
|
1163
|
+
return this.buildUnionQuery('union', others);
|
|
1164
|
+
}
|
|
1165
|
+
buildUnionQuery(separator, others) {
|
|
1166
|
+
const all = [this, ...others];
|
|
1167
|
+
const parts = [];
|
|
1168
|
+
const params = [];
|
|
1169
|
+
for (const qb of all) {
|
|
1170
|
+
const compiled = qb instanceof _a ? qb.toQuery() : qb.compile();
|
|
1171
|
+
parts.push(`(${compiled.sql})`);
|
|
1172
|
+
params.push(...compiled.params);
|
|
1173
|
+
}
|
|
1174
|
+
const result = this.clone(true);
|
|
1175
|
+
result.#state.unionQuery = { sql: parts.join(` ${separator} `), params };
|
|
1176
|
+
return result;
|
|
1177
|
+
}
|
|
1178
|
+
with(name, query, options) {
|
|
1179
|
+
return this.addCte(name, query, options);
|
|
1180
|
+
}
|
|
1181
|
+
withRecursive(name, query, options) {
|
|
1182
|
+
return this.addCte(name, query, options, true);
|
|
1183
|
+
}
|
|
1184
|
+
addCte(name, query, options, recursive) {
|
|
1185
|
+
this.ensureNotFinalized();
|
|
1186
|
+
if (this.#state.ctes.some(cte => cte.name === name)) {
|
|
1187
|
+
throw new Error(`CTE with name '${name}' already exists`);
|
|
1188
|
+
}
|
|
1189
|
+
// Eagerly compile QueryBuilder to RawQueryFragment — later mutations to the sub-query won't be reflected
|
|
1190
|
+
const compiled = query instanceof _a ? query.toRaw() : query;
|
|
1191
|
+
this.#state.ctes.push({
|
|
1192
|
+
name,
|
|
1193
|
+
query: compiled,
|
|
1194
|
+
recursive,
|
|
1195
|
+
columns: options?.columns,
|
|
1196
|
+
materialized: options?.materialized,
|
|
1197
|
+
});
|
|
1198
|
+
return this;
|
|
1199
|
+
}
|
|
1062
1200
|
clone(reset, preserve) {
|
|
1063
|
-
const qb = new
|
|
1064
|
-
reset
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
continue;
|
|
1201
|
+
const qb = new _a(this.#state.mainAlias.entityName, this.metadata, this.driver, this.context, this.#state.mainAlias.aliasName, this.connectionType, this.em);
|
|
1202
|
+
if (reset !== true) {
|
|
1203
|
+
qb.#state = Utils.copy(this.#state);
|
|
1204
|
+
// CTEs contain NativeQueryBuilder instances that should not be deep-cloned
|
|
1205
|
+
qb.#state.ctes = this.#state.ctes.map(cte => ({ ...cte }));
|
|
1206
|
+
if (Array.isArray(reset)) {
|
|
1207
|
+
const fresh = _a.createDefaultState();
|
|
1208
|
+
for (const key of reset) {
|
|
1209
|
+
qb.#state[key] = fresh[key];
|
|
1210
|
+
}
|
|
1074
1211
|
}
|
|
1075
|
-
qb[prop] = properties.includes(prop) ? Utils.copy(this[prop]) : this[prop];
|
|
1076
1212
|
}
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1213
|
+
else if (preserve) {
|
|
1214
|
+
for (const key of preserve) {
|
|
1215
|
+
qb.#state[key] = Utils.copy(this.#state[key]);
|
|
1216
|
+
}
|
|
1080
1217
|
}
|
|
1081
|
-
qb.
|
|
1082
|
-
qb
|
|
1083
|
-
qb
|
|
1218
|
+
qb.#state.finalized = false;
|
|
1219
|
+
qb.#query = undefined;
|
|
1220
|
+
qb.#helper = qb.createQueryBuilderHelper();
|
|
1084
1221
|
return qb;
|
|
1085
1222
|
}
|
|
1086
1223
|
/**
|
|
@@ -1100,11 +1237,11 @@ export class QueryBuilder {
|
|
|
1100
1237
|
if (typeof meta.expression === 'string') {
|
|
1101
1238
|
return `(${meta.expression}) as ${this.platform.quoteIdentifier(this.alias)}`;
|
|
1102
1239
|
}
|
|
1103
|
-
const res = meta.expression(this.em, this.
|
|
1240
|
+
const res = meta.expression(this.em, this.#state.cond, {});
|
|
1104
1241
|
if (typeof res === 'string') {
|
|
1105
1242
|
return `(${res}) as ${this.platform.quoteIdentifier(this.alias)}`;
|
|
1106
1243
|
}
|
|
1107
|
-
if (res instanceof
|
|
1244
|
+
if (res instanceof _a) {
|
|
1108
1245
|
return `(${res.getFormattedQuery()}) as ${this.platform.quoteIdentifier(this.alias)}`;
|
|
1109
1246
|
}
|
|
1110
1247
|
if (isRaw(res)) {
|
|
@@ -1123,10 +1260,11 @@ export class QueryBuilder {
|
|
|
1123
1260
|
addPropertyJoin(prop, ownerAlias, alias, type, path, schema) {
|
|
1124
1261
|
schema ??= prop.targetMeta?.schema === '*' ? '*' : this.driver.getSchemaName(prop.targetMeta);
|
|
1125
1262
|
const key = `[tpt]${ownerAlias}#${alias}`;
|
|
1126
|
-
this.
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1263
|
+
this.#state.joins[key] =
|
|
1264
|
+
prop.kind === ReferenceKind.MANY_TO_ONE
|
|
1265
|
+
? this.helper.joinManyToOneReference(prop, ownerAlias, alias, type, {}, schema)
|
|
1266
|
+
: this.helper.joinOneToReference(prop, ownerAlias, alias, type, {}, schema);
|
|
1267
|
+
this.#state.joins[key].path = path;
|
|
1130
1268
|
return key;
|
|
1131
1269
|
}
|
|
1132
1270
|
joinReference(field, alias, cond, type, path, schema, subquery) {
|
|
@@ -1136,7 +1274,7 @@ export class QueryBuilder {
|
|
|
1136
1274
|
name: '__subquery__',
|
|
1137
1275
|
kind: ReferenceKind.MANY_TO_ONE,
|
|
1138
1276
|
};
|
|
1139
|
-
if (field instanceof
|
|
1277
|
+
if (field instanceof _a) {
|
|
1140
1278
|
prop.type = Utils.className(field.mainAlias.entityName);
|
|
1141
1279
|
prop.targetMeta = field.mainAlias.meta;
|
|
1142
1280
|
field = field.getNativeQuery();
|
|
@@ -1145,7 +1283,7 @@ export class QueryBuilder {
|
|
|
1145
1283
|
field = this.platform.formatQuery(field.sql, field.params);
|
|
1146
1284
|
}
|
|
1147
1285
|
const key = `${this.alias}.${prop.name}#${alias}`;
|
|
1148
|
-
this.
|
|
1286
|
+
this.#state.joins[key] = {
|
|
1149
1287
|
prop,
|
|
1150
1288
|
alias,
|
|
1151
1289
|
type,
|
|
@@ -1161,10 +1299,10 @@ export class QueryBuilder {
|
|
|
1161
1299
|
}
|
|
1162
1300
|
const [fromAlias, fromField] = this.helper.splitField(field);
|
|
1163
1301
|
const q = (str) => `'${str}'`;
|
|
1164
|
-
if (!this.
|
|
1165
|
-
throw new Error(`Trying to join ${q(fromField)} with alias ${q(fromAlias)}, but ${q(fromAlias)} is not a known alias. Available aliases are: ${Object.keys(this.
|
|
1302
|
+
if (!this.#state.aliases[fromAlias]) {
|
|
1303
|
+
throw new Error(`Trying to join ${q(fromField)} with alias ${q(fromAlias)}, but ${q(fromAlias)} is not a known alias. Available aliases are: ${Object.keys(this.#state.aliases).map(q).join(', ')}.`);
|
|
1166
1304
|
}
|
|
1167
|
-
const entityName = this.
|
|
1305
|
+
const entityName = this.#state.aliases[fromAlias].entityName;
|
|
1168
1306
|
const meta = this.metadata.get(entityName);
|
|
1169
1307
|
const prop = meta.properties[fromField];
|
|
1170
1308
|
if (!prop) {
|
|
@@ -1172,7 +1310,7 @@ export class QueryBuilder {
|
|
|
1172
1310
|
}
|
|
1173
1311
|
// For TPT inheritance, owning relations (M:1 and owning 1:1) may have FK columns in a parent table
|
|
1174
1312
|
// Resolve the correct alias for the table that owns the FK column
|
|
1175
|
-
const ownerAlias =
|
|
1313
|
+
const ownerAlias = prop.kind === ReferenceKind.MANY_TO_ONE || (prop.kind === ReferenceKind.ONE_TO_ONE && prop.owner)
|
|
1176
1314
|
? this.helper.getTPTAliasForProperty(fromField, fromAlias)
|
|
1177
1315
|
: fromAlias;
|
|
1178
1316
|
this.createAlias(prop.targetMeta.class, alias);
|
|
@@ -1187,10 +1325,10 @@ export class QueryBuilder {
|
|
|
1187
1325
|
const criteriaNode = CriteriaNodeFactory.createNode(this.metadata, prop.targetMeta.class, cond);
|
|
1188
1326
|
cond = criteriaNode.process(this, { ignoreBranching: true, alias });
|
|
1189
1327
|
let aliasedName = `${fromAlias}.${prop.name}#${alias}`;
|
|
1190
|
-
path ??= `${
|
|
1328
|
+
path ??= `${Object.values(this.#state.joins).find(j => j.alias === fromAlias)?.path ?? Utils.className(entityName)}.${prop.name}`;
|
|
1191
1329
|
if (prop.kind === ReferenceKind.ONE_TO_MANY) {
|
|
1192
|
-
this.
|
|
1193
|
-
this.
|
|
1330
|
+
this.#state.joins[aliasedName] = this.helper.joinOneToReference(prop, fromAlias, alias, type, cond, schema);
|
|
1331
|
+
this.#state.joins[aliasedName].path ??= path;
|
|
1194
1332
|
}
|
|
1195
1333
|
else if (prop.kind === ReferenceKind.MANY_TO_MANY) {
|
|
1196
1334
|
let pivotAlias = alias;
|
|
@@ -1200,18 +1338,19 @@ export class QueryBuilder {
|
|
|
1200
1338
|
aliasedName = `${fromAlias}.${prop.name}#${pivotAlias}`;
|
|
1201
1339
|
}
|
|
1202
1340
|
const joins = this.helper.joinManyToManyReference(prop, fromAlias, alias, pivotAlias, type, cond, path, schema);
|
|
1203
|
-
Object.assign(this.
|
|
1341
|
+
Object.assign(this.#state.joins, joins);
|
|
1204
1342
|
this.createAlias(prop.pivotEntity, pivotAlias);
|
|
1205
|
-
this.
|
|
1343
|
+
this.#state.joins[aliasedName].path ??= path;
|
|
1206
1344
|
aliasedName = Object.keys(joins)[1];
|
|
1207
1345
|
}
|
|
1208
1346
|
else if (prop.kind === ReferenceKind.ONE_TO_ONE) {
|
|
1209
|
-
this.
|
|
1210
|
-
this.
|
|
1347
|
+
this.#state.joins[aliasedName] = this.helper.joinOneToReference(prop, ownerAlias, alias, type, cond, schema);
|
|
1348
|
+
this.#state.joins[aliasedName].path ??= path;
|
|
1211
1349
|
}
|
|
1212
|
-
else {
|
|
1213
|
-
|
|
1214
|
-
this.
|
|
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;
|
|
1215
1354
|
}
|
|
1216
1355
|
return { prop, key: aliasedName };
|
|
1217
1356
|
}
|
|
@@ -1229,14 +1368,14 @@ export class QueryBuilder {
|
|
|
1229
1368
|
// Strip 'as alias' suffix if present — the alias is passed to mapper at the end
|
|
1230
1369
|
let field = originalField;
|
|
1231
1370
|
let customAlias;
|
|
1232
|
-
const asMatch =
|
|
1371
|
+
const asMatch = FIELD_ALIAS_RE.exec(originalField);
|
|
1233
1372
|
if (asMatch) {
|
|
1234
1373
|
field = asMatch[1].trim();
|
|
1235
1374
|
customAlias = asMatch[2];
|
|
1236
1375
|
}
|
|
1237
|
-
const join = Object.keys(this.
|
|
1376
|
+
const join = Object.keys(this.#state.joins).find(k => field === k.substring(0, k.indexOf('#')));
|
|
1238
1377
|
if (join && type === 'where') {
|
|
1239
|
-
ret.push(...this.helper.mapJoinColumns(this.type, this.
|
|
1378
|
+
ret.push(...this.helper.mapJoinColumns(this.type, this.#state.joins[join]));
|
|
1240
1379
|
return;
|
|
1241
1380
|
}
|
|
1242
1381
|
const [a, f] = this.helper.splitField(field);
|
|
@@ -1250,7 +1389,7 @@ export class QueryBuilder {
|
|
|
1250
1389
|
}
|
|
1251
1390
|
if (prop?.embedded || (prop?.kind === ReferenceKind.EMBEDDED && prop.object)) {
|
|
1252
1391
|
const name = prop.embeddedPath?.join('.') ?? prop.fieldNames[0];
|
|
1253
|
-
const aliased = this.
|
|
1392
|
+
const aliased = this.#state.aliases[a] ? `${a}.${name}` : name;
|
|
1254
1393
|
ret.push(getFieldName(aliased, customAlias));
|
|
1255
1394
|
return;
|
|
1256
1395
|
}
|
|
@@ -1260,7 +1399,9 @@ export class QueryBuilder {
|
|
|
1260
1399
|
}
|
|
1261
1400
|
const nest = (prop) => {
|
|
1262
1401
|
for (const childProp of Object.values(prop.embeddedProps)) {
|
|
1263
|
-
if (childProp.fieldNames &&
|
|
1402
|
+
if (childProp.fieldNames &&
|
|
1403
|
+
(childProp.kind !== ReferenceKind.EMBEDDED || childProp.object) &&
|
|
1404
|
+
childProp.persist !== false) {
|
|
1264
1405
|
ret.push(getFieldName(childProp.fieldNames[0]));
|
|
1265
1406
|
}
|
|
1266
1407
|
else {
|
|
@@ -1281,16 +1422,16 @@ export class QueryBuilder {
|
|
|
1281
1422
|
ret.push(getFieldName(field, customAlias));
|
|
1282
1423
|
});
|
|
1283
1424
|
const requiresSQLConversion = this.mainAlias.meta.props.filter(p => p.hasConvertToJSValueSQL && p.persist !== false);
|
|
1284
|
-
if (this.flags.has(QueryFlag.CONVERT_CUSTOM_TYPES) &&
|
|
1425
|
+
if (this.#state.flags.has(QueryFlag.CONVERT_CUSTOM_TYPES) &&
|
|
1285
1426
|
(fields.includes('*') || fields.includes(`${this.mainAlias.aliasName}.*`)) &&
|
|
1286
1427
|
requiresSQLConversion.length > 0) {
|
|
1287
1428
|
for (const p of requiresSQLConversion) {
|
|
1288
1429
|
ret.push(this.helper.mapper(p.name, this.type));
|
|
1289
1430
|
}
|
|
1290
1431
|
}
|
|
1291
|
-
for (const f of Object.keys(this.
|
|
1292
|
-
if (type === 'where' && this.
|
|
1293
|
-
ret.push(...this.helper.mapJoinColumns(this.type, this.
|
|
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]));
|
|
1294
1435
|
}
|
|
1295
1436
|
}
|
|
1296
1437
|
return Utils.unique(ret);
|
|
@@ -1306,14 +1447,16 @@ export class QueryBuilder {
|
|
|
1306
1447
|
}
|
|
1307
1448
|
const parts = field.split('.');
|
|
1308
1449
|
// Simple alias.property case - let prepareFields handle it
|
|
1309
|
-
if (parts.length === 2 && this.
|
|
1450
|
+
if (parts.length === 2 && this.#state.aliases[parts[0]]) {
|
|
1310
1451
|
return field;
|
|
1311
1452
|
}
|
|
1312
1453
|
// Start with root alias
|
|
1313
1454
|
let currentAlias = parts[0];
|
|
1314
|
-
let currentMeta = this.
|
|
1455
|
+
let currentMeta = this.#state.aliases[currentAlias]
|
|
1456
|
+
? this.metadata.get(this.#state.aliases[currentAlias].entityName)
|
|
1457
|
+
: this.mainAlias.meta;
|
|
1315
1458
|
// If first part is not an alias, it's a property of the main entity
|
|
1316
|
-
if (!this.
|
|
1459
|
+
if (!this.#state.aliases[currentAlias]) {
|
|
1317
1460
|
currentAlias = this.mainAlias.aliasName;
|
|
1318
1461
|
parts.unshift(currentAlias);
|
|
1319
1462
|
}
|
|
@@ -1351,14 +1494,14 @@ export class QueryBuilder {
|
|
|
1351
1494
|
}
|
|
1352
1495
|
// Find existing join or create new one
|
|
1353
1496
|
const joinPath = parts.slice(0, i + 1).join('.');
|
|
1354
|
-
const existingJoinKey = Object.keys(this.
|
|
1355
|
-
const join = this.
|
|
1497
|
+
const existingJoinKey = Object.keys(this.#state.joins).find(k => {
|
|
1498
|
+
const join = this.#state.joins[k];
|
|
1356
1499
|
// Check by path or by key prefix (key format is `alias.field#joinAlias`)
|
|
1357
1500
|
return join.path === joinPath || k.startsWith(`${currentAlias}.${propName}#`);
|
|
1358
1501
|
});
|
|
1359
1502
|
let joinAlias;
|
|
1360
1503
|
if (existingJoinKey) {
|
|
1361
|
-
joinAlias = this.
|
|
1504
|
+
joinAlias = this.#state.joins[existingJoinKey].alias;
|
|
1362
1505
|
}
|
|
1363
1506
|
else {
|
|
1364
1507
|
joinAlias = this.getNextAlias(prop.targetMeta?.className ?? propName);
|
|
@@ -1375,18 +1518,18 @@ export class QueryBuilder {
|
|
|
1375
1518
|
}
|
|
1376
1519
|
init(type, data, cond) {
|
|
1377
1520
|
this.ensureNotFinalized();
|
|
1378
|
-
this.
|
|
1379
|
-
if ([QueryType.UPDATE, QueryType.DELETE].includes(type) && Utils.hasObjectKeys(this.
|
|
1521
|
+
this.#state.type = type;
|
|
1522
|
+
if ([QueryType.UPDATE, QueryType.DELETE].includes(type) && Utils.hasObjectKeys(this.#state.cond)) {
|
|
1380
1523
|
throw new Error(`You are trying to call \`qb.where().${type.toLowerCase()}()\`. Calling \`qb.${type.toLowerCase()}()\` before \`qb.where()\` is required.`);
|
|
1381
1524
|
}
|
|
1382
1525
|
if (!this.helper.isTableNameAliasRequired(type)) {
|
|
1383
|
-
|
|
1526
|
+
this.#state.fields = undefined;
|
|
1384
1527
|
}
|
|
1385
1528
|
if (data) {
|
|
1386
1529
|
if (Utils.isEntity(data)) {
|
|
1387
1530
|
data = this.em?.getComparator().prepareEntity(data) ?? serialize(data);
|
|
1388
1531
|
}
|
|
1389
|
-
this.
|
|
1532
|
+
this.#state.data = this.helper.processData(data, this.#state.flags.has(QueryFlag.CONVERT_CUSTOM_TYPES), false);
|
|
1390
1533
|
}
|
|
1391
1534
|
if (cond) {
|
|
1392
1535
|
this.where(cond);
|
|
@@ -1394,47 +1537,54 @@ export class QueryBuilder {
|
|
|
1394
1537
|
return this;
|
|
1395
1538
|
}
|
|
1396
1539
|
getQueryBase(processVirtualEntity) {
|
|
1397
|
-
const qb = this.platform.createNativeQueryBuilder().setFlags(this.flags);
|
|
1398
|
-
const { subQuery, aliasName, entityName, meta } = this.mainAlias;
|
|
1399
|
-
const requiresAlias = this.finalized && (this.
|
|
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));
|
|
1400
1543
|
const alias = requiresAlias ? aliasName : undefined;
|
|
1401
1544
|
const schema = this.getSchema(this.mainAlias);
|
|
1402
|
-
const tableName =
|
|
1403
|
-
|
|
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;
|
|
1404
1554
|
if (meta.virtual && processVirtualEntity) {
|
|
1405
|
-
qb.from(raw(this.fromVirtual(meta)), { indexHint: this.
|
|
1555
|
+
qb.from(raw(this.fromVirtual(meta)), { indexHint: this.#state.indexHint });
|
|
1406
1556
|
}
|
|
1407
1557
|
else {
|
|
1408
1558
|
qb.from(tableName, {
|
|
1409
|
-
schema,
|
|
1559
|
+
schema: rawTableName ? undefined : schema,
|
|
1410
1560
|
alias,
|
|
1411
|
-
indexHint: this.
|
|
1561
|
+
indexHint: this.#state.indexHint,
|
|
1412
1562
|
});
|
|
1413
1563
|
}
|
|
1414
1564
|
switch (this.type) {
|
|
1415
1565
|
case QueryType.SELECT:
|
|
1416
|
-
qb.select(this.prepareFields(this.
|
|
1417
|
-
if (this.
|
|
1418
|
-
qb.distinctOn(this.prepareFields(this.
|
|
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));
|
|
1419
1569
|
}
|
|
1420
|
-
else if (this.flags.has(QueryFlag.DISTINCT)) {
|
|
1570
|
+
else if (this.#state.flags.has(QueryFlag.DISTINCT)) {
|
|
1421
1571
|
qb.distinct();
|
|
1422
1572
|
}
|
|
1423
|
-
this.helper.processJoins(qb, this.
|
|
1573
|
+
this.helper.processJoins(qb, this.#state.joins, joinSchema, schemaOverride);
|
|
1424
1574
|
break;
|
|
1425
1575
|
case QueryType.COUNT: {
|
|
1426
|
-
const fields = this.
|
|
1427
|
-
qb.count(fields, this.flags.has(QueryFlag.DISTINCT));
|
|
1428
|
-
this.helper.processJoins(qb, this.
|
|
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);
|
|
1429
1579
|
break;
|
|
1430
1580
|
}
|
|
1431
1581
|
case QueryType.INSERT:
|
|
1432
|
-
qb.insert(this.
|
|
1582
|
+
qb.insert(this.#state.data);
|
|
1433
1583
|
break;
|
|
1434
1584
|
case QueryType.UPDATE:
|
|
1435
|
-
qb.update(this.
|
|
1436
|
-
this.helper.processJoins(qb, this.
|
|
1437
|
-
this.helper.updateVersionProperty(qb, this.
|
|
1585
|
+
qb.update(this.#state.data);
|
|
1586
|
+
this.helper.processJoins(qb, this.#state.joins, joinSchema, schemaOverride);
|
|
1587
|
+
this.helper.updateVersionProperty(qb, this.#state.data);
|
|
1438
1588
|
break;
|
|
1439
1589
|
case QueryType.DELETE:
|
|
1440
1590
|
qb.delete();
|
|
@@ -1460,7 +1610,9 @@ export class QueryBuilder {
|
|
|
1460
1610
|
};
|
|
1461
1611
|
lookUpChildren(children, meta.class);
|
|
1462
1612
|
this.andWhere({
|
|
1463
|
-
[meta.root.discriminatorColumn]: children.length > 0
|
|
1613
|
+
[meta.root.discriminatorColumn]: children.length > 0
|
|
1614
|
+
? { $in: [meta.discriminatorValue, ...children.map(c => c.discriminatorValue)] }
|
|
1615
|
+
: meta.discriminatorValue,
|
|
1464
1616
|
});
|
|
1465
1617
|
}
|
|
1466
1618
|
/**
|
|
@@ -1478,20 +1630,22 @@ export class QueryBuilder {
|
|
|
1478
1630
|
*/
|
|
1479
1631
|
applyTPTJoins() {
|
|
1480
1632
|
const meta = this.mainAlias.meta;
|
|
1481
|
-
if (meta?.inheritanceType !== 'tpt' ||
|
|
1633
|
+
if (meta?.inheritanceType !== 'tpt' ||
|
|
1634
|
+
!meta.tptParent ||
|
|
1635
|
+
![QueryType.SELECT, QueryType.COUNT].includes(this.type)) {
|
|
1482
1636
|
return;
|
|
1483
1637
|
}
|
|
1484
|
-
if (this.tptJoinsApplied) {
|
|
1638
|
+
if (this.#state.tptJoinsApplied) {
|
|
1485
1639
|
return;
|
|
1486
1640
|
}
|
|
1487
|
-
this.tptJoinsApplied = true;
|
|
1641
|
+
this.#state.tptJoinsApplied = true;
|
|
1488
1642
|
let childMeta = meta;
|
|
1489
1643
|
let childAlias = this.mainAlias.aliasName;
|
|
1490
1644
|
while (childMeta.tptParent) {
|
|
1491
1645
|
const parentMeta = childMeta.tptParent;
|
|
1492
1646
|
const parentAlias = this.getNextAlias(parentMeta.className);
|
|
1493
1647
|
this.createAlias(parentMeta.class, parentAlias);
|
|
1494
|
-
this.
|
|
1648
|
+
this.#state.tptAlias[parentMeta.className] = parentAlias;
|
|
1495
1649
|
this.addPropertyJoin(childMeta.tptParentProp, childAlias, parentAlias, JoinType.innerJoin, `[tpt]${childMeta.className}`);
|
|
1496
1650
|
childMeta = parentMeta;
|
|
1497
1651
|
childAlias = parentAlias;
|
|
@@ -1502,20 +1656,22 @@ export class QueryBuilder {
|
|
|
1502
1656
|
*/
|
|
1503
1657
|
addTPTParentFields() {
|
|
1504
1658
|
const meta = this.mainAlias.meta;
|
|
1505
|
-
if (meta?.inheritanceType !== 'tpt' ||
|
|
1659
|
+
if (meta?.inheritanceType !== 'tpt' ||
|
|
1660
|
+
!meta.tptParent ||
|
|
1661
|
+
![QueryType.SELECT, QueryType.COUNT].includes(this.type)) {
|
|
1506
1662
|
return;
|
|
1507
1663
|
}
|
|
1508
|
-
if (!this.
|
|
1664
|
+
if (!this.#state.fields?.includes('*') && !this.#state.fields?.includes(`${this.mainAlias.aliasName}.*`)) {
|
|
1509
1665
|
return;
|
|
1510
1666
|
}
|
|
1511
1667
|
let parentMeta = meta.tptParent;
|
|
1512
1668
|
while (parentMeta) {
|
|
1513
|
-
const parentAlias = this.
|
|
1669
|
+
const parentAlias = this.#state.tptAlias[parentMeta.className];
|
|
1514
1670
|
if (parentAlias) {
|
|
1515
1671
|
const schema = parentMeta.schema === '*' ? '*' : this.driver.getSchemaName(parentMeta);
|
|
1516
|
-
parentMeta
|
|
1517
|
-
.filter(prop => this.platform.shouldHaveColumn(prop, []))
|
|
1518
|
-
.forEach(prop => this.
|
|
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)));
|
|
1519
1675
|
}
|
|
1520
1676
|
parentMeta = parentMeta.tptParent;
|
|
1521
1677
|
}
|
|
@@ -1530,32 +1686,32 @@ export class QueryBuilder {
|
|
|
1530
1686
|
if (!descendants?.length || ![QueryType.SELECT, QueryType.COUNT].includes(this.type)) {
|
|
1531
1687
|
return;
|
|
1532
1688
|
}
|
|
1533
|
-
if (!this.
|
|
1689
|
+
if (!this.#state.fields?.includes('*') && !this.#state.fields?.includes(`${this.mainAlias.aliasName}.*`)) {
|
|
1534
1690
|
return;
|
|
1535
1691
|
}
|
|
1536
1692
|
// LEFT JOIN each descendant table and add their fields
|
|
1537
1693
|
for (const childMeta of descendants) {
|
|
1538
1694
|
const childAlias = this.getNextAlias(childMeta.className);
|
|
1539
1695
|
this.createAlias(childMeta.class, childAlias);
|
|
1540
|
-
this.
|
|
1696
|
+
this.#state.tptAlias[childMeta.className] = childAlias;
|
|
1541
1697
|
this.addPropertyJoin(childMeta.tptInverseProp, this.mainAlias.aliasName, childAlias, JoinType.leftJoin, `[tpt]${meta.className}`);
|
|
1542
1698
|
// Add child fields
|
|
1543
1699
|
const schema = childMeta.schema === '*' ? '*' : this.driver.getSchemaName(childMeta);
|
|
1544
|
-
childMeta
|
|
1545
|
-
.filter(prop => !prop.primary && this.platform.shouldHaveColumn(prop, []))
|
|
1546
|
-
.forEach(prop => this.
|
|
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)));
|
|
1547
1703
|
}
|
|
1548
1704
|
// Add computed discriminator (CASE WHEN to determine concrete type)
|
|
1549
1705
|
// descendants is pre-sorted by depth (deepest first) during discovery
|
|
1550
1706
|
if (meta.tptDiscriminatorColumn) {
|
|
1551
|
-
this.
|
|
1707
|
+
this.#state.fields.push(this.driver.buildTPTDiscriminatorExpression(meta, descendants, this.#state.tptAlias, this.mainAlias.aliasName));
|
|
1552
1708
|
}
|
|
1553
1709
|
}
|
|
1554
1710
|
finalize() {
|
|
1555
|
-
if (this.finalized) {
|
|
1711
|
+
if (this.#state.finalized) {
|
|
1556
1712
|
return;
|
|
1557
1713
|
}
|
|
1558
|
-
if (!this.
|
|
1714
|
+
if (!this.#state.type) {
|
|
1559
1715
|
this.select('*');
|
|
1560
1716
|
}
|
|
1561
1717
|
const meta = this.mainAlias.meta;
|
|
@@ -1565,93 +1721,102 @@ export class QueryBuilder {
|
|
|
1565
1721
|
this.applyTPTPolymorphicJoins();
|
|
1566
1722
|
this.processPopulateHint();
|
|
1567
1723
|
this.processNestedJoins();
|
|
1568
|
-
if (meta && (this.
|
|
1724
|
+
if (meta && (this.#state.fields?.includes('*') || this.#state.fields?.includes(`${this.mainAlias.aliasName}.*`))) {
|
|
1569
1725
|
const schema = this.getSchema(this.mainAlias);
|
|
1570
1726
|
// Create a column mapping with unquoted aliases - quoting should be handled by the user via `quote` helper
|
|
1571
1727
|
// For TPT, use helper to resolve correct alias per property (inherited props use parent alias)
|
|
1572
1728
|
const quotedMainAlias = this.platform.quoteIdentifier(this.mainAlias.aliasName).toString();
|
|
1573
1729
|
const columns = meta.createColumnMappingObject(prop => this.helper.getTPTAliasForProperty(prop.name, this.mainAlias.aliasName), quotedMainAlias);
|
|
1574
1730
|
meta.props
|
|
1575
|
-
.filter(prop => prop.formula && (!prop.lazy || this.flags.has(QueryFlag.INCLUDE_LAZY_FORMULAS)))
|
|
1731
|
+
.filter(prop => prop.formula && (!prop.lazy || this.#state.flags.has(QueryFlag.INCLUDE_LAZY_FORMULAS)))
|
|
1576
1732
|
.map(prop => {
|
|
1577
1733
|
const aliased = this.platform.quoteIdentifier(prop.fieldNames[0]);
|
|
1578
1734
|
const table = this.helper.createFormulaTable(quotedMainAlias, meta, schema);
|
|
1579
1735
|
return `${this.driver.evaluateFormula(prop.formula, columns, table)} as ${aliased}`;
|
|
1580
1736
|
})
|
|
1581
|
-
.filter(field => !this.
|
|
1737
|
+
.filter(field => !this.#state.fields.some(f => {
|
|
1582
1738
|
if (isRaw(f)) {
|
|
1583
1739
|
return f.sql === field && f.params.length === 0;
|
|
1584
1740
|
}
|
|
1585
1741
|
return f === field;
|
|
1586
1742
|
}))
|
|
1587
|
-
.forEach(field => this.
|
|
1743
|
+
.forEach(field => this.#state.fields.push(raw(field)));
|
|
1588
1744
|
}
|
|
1589
|
-
QueryHelper.processObjectParams(this.
|
|
1590
|
-
QueryHelper.processObjectParams(this.
|
|
1591
|
-
QueryHelper.processObjectParams(this.
|
|
1745
|
+
QueryHelper.processObjectParams(this.#state.data);
|
|
1746
|
+
QueryHelper.processObjectParams(this.#state.cond);
|
|
1747
|
+
QueryHelper.processObjectParams(this.#state.having);
|
|
1592
1748
|
// automatically enable paginate flag when we detect to-many joins, but only if there is no `group by` clause
|
|
1593
|
-
if (!this.flags.has(QueryFlag.DISABLE_PAGINATE) &&
|
|
1594
|
-
this.
|
|
1595
|
-
|
|
1596
|
-
|
|
1749
|
+
if (!this.#state.flags.has(QueryFlag.DISABLE_PAGINATE) &&
|
|
1750
|
+
this.#state.groupBy.length === 0 &&
|
|
1751
|
+
this.hasToManyJoins()) {
|
|
1752
|
+
this.#state.flags.add(QueryFlag.PAGINATE);
|
|
1753
|
+
}
|
|
1754
|
+
if (meta &&
|
|
1755
|
+
!meta.virtual &&
|
|
1756
|
+
this.#state.flags.has(QueryFlag.PAGINATE) &&
|
|
1757
|
+
!this.#state.flags.has(QueryFlag.DISABLE_PAGINATE) &&
|
|
1758
|
+
(this.#state.limit > 0 || this.#state.offset > 0)) {
|
|
1597
1759
|
this.wrapPaginateSubQuery(meta);
|
|
1598
1760
|
}
|
|
1599
|
-
if (meta &&
|
|
1761
|
+
if (meta &&
|
|
1762
|
+
(this.#state.flags.has(QueryFlag.UPDATE_SUB_QUERY) || this.#state.flags.has(QueryFlag.DELETE_SUB_QUERY))) {
|
|
1600
1763
|
this.wrapModifySubQuery(meta);
|
|
1601
1764
|
}
|
|
1602
|
-
this.finalized = true;
|
|
1765
|
+
this.#state.finalized = true;
|
|
1603
1766
|
}
|
|
1604
1767
|
/** @internal */
|
|
1605
1768
|
processPopulateHint() {
|
|
1606
|
-
if (this.populateHintFinalized) {
|
|
1769
|
+
if (this.#state.populateHintFinalized) {
|
|
1607
1770
|
return;
|
|
1608
1771
|
}
|
|
1609
1772
|
const meta = this.mainAlias.meta;
|
|
1610
|
-
if (meta && this.flags.has(QueryFlag.AUTO_JOIN_ONE_TO_ONE_OWNER)) {
|
|
1611
|
-
const relationsToPopulate = this.
|
|
1773
|
+
if (meta && this.#state.flags.has(QueryFlag.AUTO_JOIN_ONE_TO_ONE_OWNER)) {
|
|
1774
|
+
const relationsToPopulate = this.#state.populate.map(({ field }) => field);
|
|
1612
1775
|
meta.relations
|
|
1613
|
-
.filter(prop => prop.kind === ReferenceKind.ONE_TO_ONE &&
|
|
1776
|
+
.filter(prop => prop.kind === ReferenceKind.ONE_TO_ONE &&
|
|
1777
|
+
!prop.owner &&
|
|
1778
|
+
!relationsToPopulate.includes(prop.name) &&
|
|
1779
|
+
!relationsToPopulate.includes(`${prop.name}:ref`))
|
|
1614
1780
|
.map(prop => ({ field: `${prop.name}:ref` }))
|
|
1615
|
-
.forEach(item => this.
|
|
1781
|
+
.forEach(item => this.#state.populate.push(item));
|
|
1616
1782
|
}
|
|
1617
|
-
this.
|
|
1783
|
+
this.#state.populate.forEach(({ field }) => {
|
|
1618
1784
|
const [fromAlias, fromField] = this.helper.splitField(field);
|
|
1619
1785
|
const aliasedField = `${fromAlias}.${fromField}`;
|
|
1620
|
-
const join = Object.keys(this.
|
|
1621
|
-
if (join && this.
|
|
1622
|
-
this.
|
|
1786
|
+
const join = Object.keys(this.#state.joins).find(k => `${aliasedField}#${this.#state.joins[k].alias}` === k);
|
|
1787
|
+
if (join && this.#state.joins[join] && this.helper.isOneToOneInverse(fromField)) {
|
|
1788
|
+
this.#state.populateMap[join] = this.#state.joins[join].alias;
|
|
1623
1789
|
return;
|
|
1624
1790
|
}
|
|
1625
1791
|
if (meta && this.helper.isOneToOneInverse(fromField)) {
|
|
1626
1792
|
const prop = meta.properties[fromField];
|
|
1627
1793
|
const alias = this.getNextAlias(prop.pivotEntity ?? prop.targetMeta.class);
|
|
1628
1794
|
const aliasedName = `${fromAlias}.${prop.name}#${alias}`;
|
|
1629
|
-
this.
|
|
1630
|
-
this.
|
|
1631
|
-
|
|
1795
|
+
this.#state.joins[aliasedName] = this.helper.joinOneToReference(prop, this.mainAlias.aliasName, alias, JoinType.leftJoin);
|
|
1796
|
+
this.#state.joins[aliasedName].path =
|
|
1797
|
+
`${Object.values(this.#state.joins).find(j => j.alias === fromAlias)?.path ?? meta.className}.${prop.name}`;
|
|
1798
|
+
this.#state.populateMap[aliasedName] = this.#state.joins[aliasedName].alias;
|
|
1632
1799
|
this.createAlias(prop.targetMeta.class, alias);
|
|
1633
1800
|
}
|
|
1634
1801
|
});
|
|
1635
1802
|
this.processPopulateWhere(false);
|
|
1636
1803
|
this.processPopulateWhere(true);
|
|
1637
|
-
this.populateHintFinalized = true;
|
|
1804
|
+
this.#state.populateHintFinalized = true;
|
|
1638
1805
|
}
|
|
1639
1806
|
processPopulateWhere(filter) {
|
|
1640
|
-
const
|
|
1641
|
-
if (
|
|
1807
|
+
const value = filter ? this.#state.populateFilter : this.#state.populateWhere;
|
|
1808
|
+
if (value == null || value === PopulateHint.ALL) {
|
|
1642
1809
|
return;
|
|
1643
1810
|
}
|
|
1644
|
-
let joins = Object.values(this.
|
|
1811
|
+
let joins = Object.values(this.#state.joins);
|
|
1645
1812
|
for (const join of joins) {
|
|
1646
1813
|
join.cond_ ??= join.cond;
|
|
1647
1814
|
join.cond = { ...join.cond };
|
|
1648
1815
|
}
|
|
1649
|
-
if (typeof
|
|
1650
|
-
const cond = CriteriaNodeFactory
|
|
1651
|
-
.createNode(this.metadata, this.mainAlias.entityName, this[key])
|
|
1652
|
-
.process(this, { matchPopulateJoins: true, ignoreBranching: true, preferNoBranch: true, filter });
|
|
1816
|
+
if (typeof value === 'object') {
|
|
1817
|
+
const cond = CriteriaNodeFactory.createNode(this.metadata, this.mainAlias.entityName, value).process(this, { matchPopulateJoins: true, ignoreBranching: true, preferNoBranch: true, filter });
|
|
1653
1818
|
// there might be new joins created by processing the `populateWhere` object
|
|
1654
|
-
joins = Object.values(this.
|
|
1819
|
+
joins = Object.values(this.#state.joins);
|
|
1655
1820
|
this.mergeOnConditions(joins, cond, filter);
|
|
1656
1821
|
}
|
|
1657
1822
|
}
|
|
@@ -1671,9 +1836,11 @@ export class QueryBuilder {
|
|
|
1671
1836
|
// https://stackoverflow.com/a/56815807/3665878
|
|
1672
1837
|
if (parentJoin && !filter) {
|
|
1673
1838
|
const nested = (parentJoin.nested ??= new Set());
|
|
1674
|
-
join.type =
|
|
1675
|
-
|
|
1676
|
-
|
|
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;
|
|
1677
1844
|
nested.add(join);
|
|
1678
1845
|
}
|
|
1679
1846
|
if (join.cond[k]) {
|
|
@@ -1695,10 +1862,10 @@ export class QueryBuilder {
|
|
|
1695
1862
|
* otherwise the inner join could discard rows of the root table.
|
|
1696
1863
|
*/
|
|
1697
1864
|
processNestedJoins() {
|
|
1698
|
-
if (this.flags.has(QueryFlag.DISABLE_NESTED_INNER_JOIN)) {
|
|
1865
|
+
if (this.#state.flags.has(QueryFlag.DISABLE_NESTED_INNER_JOIN)) {
|
|
1699
1866
|
return;
|
|
1700
1867
|
}
|
|
1701
|
-
const joins = Object.values(this.
|
|
1868
|
+
const joins = Object.values(this.#state.joins);
|
|
1702
1869
|
const lookupParentGroup = (j) => {
|
|
1703
1870
|
return j.nested ?? (j.parent ? lookupParentGroup(j.parent) : undefined);
|
|
1704
1871
|
};
|
|
@@ -1707,48 +1874,44 @@ export class QueryBuilder {
|
|
|
1707
1874
|
join.parent = joins.find(j => j.alias === join.ownerAlias);
|
|
1708
1875
|
// https://stackoverflow.com/a/56815807/3665878
|
|
1709
1876
|
if (join.parent?.type === JoinType.leftJoin || join.parent?.type === JoinType.nestedLeftJoin) {
|
|
1710
|
-
const nested = (
|
|
1711
|
-
join.type = join.type === JoinType.innerJoin
|
|
1712
|
-
? JoinType.nestedInnerJoin
|
|
1713
|
-
: JoinType.nestedLeftJoin;
|
|
1877
|
+
const nested = (join.parent.nested ??= new Set());
|
|
1878
|
+
join.type = join.type === JoinType.innerJoin ? JoinType.nestedInnerJoin : JoinType.nestedLeftJoin;
|
|
1714
1879
|
nested.add(join);
|
|
1715
1880
|
}
|
|
1716
1881
|
else if (join.parent?.type === JoinType.nestedInnerJoin) {
|
|
1717
1882
|
const group = lookupParentGroup(join.parent);
|
|
1718
|
-
const nested = group ?? (
|
|
1719
|
-
join.type = join.type === JoinType.innerJoin
|
|
1720
|
-
? JoinType.nestedInnerJoin
|
|
1721
|
-
: JoinType.nestedLeftJoin;
|
|
1883
|
+
const nested = group ?? (join.parent.nested ??= new Set());
|
|
1884
|
+
join.type = join.type === JoinType.innerJoin ? JoinType.nestedInnerJoin : JoinType.nestedLeftJoin;
|
|
1722
1885
|
nested.add(join);
|
|
1723
1886
|
}
|
|
1724
1887
|
}
|
|
1725
1888
|
}
|
|
1726
1889
|
}
|
|
1727
1890
|
hasToManyJoins() {
|
|
1728
|
-
return Object.values(this.
|
|
1891
|
+
return Object.values(this.#state.joins).some(join => {
|
|
1729
1892
|
return [ReferenceKind.ONE_TO_MANY, ReferenceKind.MANY_TO_MANY].includes(join.prop.kind);
|
|
1730
1893
|
});
|
|
1731
1894
|
}
|
|
1732
1895
|
wrapPaginateSubQuery(meta) {
|
|
1733
1896
|
const schema = this.getSchema(this.mainAlias);
|
|
1734
1897
|
const pks = this.prepareFields(meta.primaryKeys, 'sub-query', schema);
|
|
1735
|
-
const subQuery = this.clone(['
|
|
1898
|
+
const subQuery = this.clone(['orderBy', 'fields', 'lockMode', 'lockTables'])
|
|
1736
1899
|
.select(pks)
|
|
1737
1900
|
.groupBy(pks)
|
|
1738
|
-
.limit(this.
|
|
1901
|
+
.limit(this.#state.limit);
|
|
1739
1902
|
// revert the on conditions added via populateWhere, we want to apply those only once
|
|
1740
|
-
for (const join of Object.values(subQuery.
|
|
1903
|
+
for (const join of Object.values(subQuery.#state.joins)) {
|
|
1741
1904
|
if (join.cond_) {
|
|
1742
1905
|
join.cond = join.cond_;
|
|
1743
1906
|
}
|
|
1744
1907
|
}
|
|
1745
|
-
if (this.
|
|
1746
|
-
subQuery.offset(this.
|
|
1908
|
+
if (this.#state.offset) {
|
|
1909
|
+
subQuery.offset(this.#state.offset);
|
|
1747
1910
|
}
|
|
1748
1911
|
const addToSelect = [];
|
|
1749
|
-
if (this.
|
|
1912
|
+
if (this.#state.orderBy.length > 0) {
|
|
1750
1913
|
const orderBy = [];
|
|
1751
|
-
for (const orderMap of this.
|
|
1914
|
+
for (const orderMap of this.#state.orderBy) {
|
|
1752
1915
|
for (const field of Utils.getObjectQueryKeys(orderMap)) {
|
|
1753
1916
|
const direction = orderMap[field];
|
|
1754
1917
|
if (RawQueryFragment.isKnownFragmentSymbol(field)) {
|
|
@@ -1769,11 +1932,11 @@ export class QueryBuilder {
|
|
|
1769
1932
|
}
|
|
1770
1933
|
subQuery.orderBy(orderBy);
|
|
1771
1934
|
}
|
|
1772
|
-
subQuery.finalized = true;
|
|
1935
|
+
subQuery.#state.finalized = true;
|
|
1773
1936
|
const innerQuery = subQuery.as(this.mainAlias.aliasName).clear('select').select(pks);
|
|
1774
1937
|
if (addToSelect.length > 0) {
|
|
1775
1938
|
addToSelect.forEach(prop => {
|
|
1776
|
-
const field = this.
|
|
1939
|
+
const field = this.#state.fields.find(field => {
|
|
1777
1940
|
if (typeof field === 'object' && field && '__as' in field) {
|
|
1778
1941
|
return field.__as === prop;
|
|
1779
1942
|
}
|
|
@@ -1799,18 +1962,20 @@ export class QueryBuilder {
|
|
|
1799
1962
|
// https://stackoverflow.com/questions/17892762/mysql-this-version-of-mysql-doesnt-yet-support-limit-in-all-any-some-subqu
|
|
1800
1963
|
const subSubQuery = this.platform.createNativeQueryBuilder();
|
|
1801
1964
|
subSubQuery.select(pks).from(innerQuery);
|
|
1802
|
-
this.
|
|
1803
|
-
this.
|
|
1965
|
+
this.#state.limit = undefined;
|
|
1966
|
+
this.#state.offset = undefined;
|
|
1804
1967
|
// Save the original WHERE conditions before pruning joins
|
|
1805
|
-
const originalCond = this.
|
|
1968
|
+
const originalCond = this.#state.cond;
|
|
1806
1969
|
const populatePaths = this.getPopulatePaths();
|
|
1807
|
-
if (!this.
|
|
1970
|
+
if (!this.#state.fields.some(field => isRaw(field))) {
|
|
1808
1971
|
this.pruneJoinsForPagination(meta, populatePaths);
|
|
1809
1972
|
}
|
|
1810
1973
|
// Transfer WHERE conditions to ORDER BY joins (GH #6160)
|
|
1811
1974
|
this.transferConditionsForOrderByJoins(meta, originalCond, populatePaths);
|
|
1812
1975
|
const { sql, params } = subSubQuery.compile();
|
|
1813
|
-
this.select(this.
|
|
1976
|
+
this.select(this.#state.fields).where({
|
|
1977
|
+
[Utils.getPrimaryKeyHash(meta.primaryKeys)]: { $in: raw(sql, params) },
|
|
1978
|
+
});
|
|
1814
1979
|
}
|
|
1815
1980
|
/**
|
|
1816
1981
|
* Computes the set of populate paths from the _populate hints.
|
|
@@ -1827,7 +1992,7 @@ export class QueryBuilder {
|
|
|
1827
1992
|
}
|
|
1828
1993
|
}
|
|
1829
1994
|
}
|
|
1830
|
-
addPath(this.
|
|
1995
|
+
addPath(this.#state.populate);
|
|
1831
1996
|
return paths;
|
|
1832
1997
|
}
|
|
1833
1998
|
normalizeJoinPath(join, meta) {
|
|
@@ -1839,14 +2004,14 @@ export class QueryBuilder {
|
|
|
1839
2004
|
* GH #6160
|
|
1840
2005
|
*/
|
|
1841
2006
|
transferConditionsForOrderByJoins(meta, cond, populatePaths) {
|
|
1842
|
-
if (!cond || this.
|
|
2007
|
+
if (!cond || this.#state.orderBy.length === 0) {
|
|
1843
2008
|
return;
|
|
1844
2009
|
}
|
|
1845
|
-
const orderByAliases = new Set(this.
|
|
2010
|
+
const orderByAliases = new Set(this.#state.orderBy
|
|
1846
2011
|
.flatMap(hint => Object.keys(hint))
|
|
1847
2012
|
.filter(k => !RawQueryFragment.isKnownFragmentSymbol(k))
|
|
1848
2013
|
.map(k => k.split('.')[0]));
|
|
1849
|
-
for (const join of Object.values(this.
|
|
2014
|
+
for (const join of Object.values(this.#state.joins)) {
|
|
1850
2015
|
const joinPath = this.normalizeJoinPath(join, meta);
|
|
1851
2016
|
const isPopulateJoin = populatePaths.has(joinPath);
|
|
1852
2017
|
// Only transfer conditions for joins used for ORDER BY but not for population
|
|
@@ -1859,10 +2024,8 @@ export class QueryBuilder {
|
|
|
1859
2024
|
* Removes joins that are not used for population or ordering to improve performance.
|
|
1860
2025
|
*/
|
|
1861
2026
|
pruneJoinsForPagination(meta, populatePaths) {
|
|
1862
|
-
const orderByAliases = this.
|
|
1863
|
-
|
|
1864
|
-
.map(k => k.split('.')[0]);
|
|
1865
|
-
const joins = Object.entries(this._joins);
|
|
2027
|
+
const orderByAliases = this.#state.orderBy.flatMap(hint => Object.keys(hint)).map(k => k.split('.')[0]);
|
|
2028
|
+
const joins = Object.entries(this.#state.joins);
|
|
1866
2029
|
const rootAlias = this.alias;
|
|
1867
2030
|
function addParentAlias(alias) {
|
|
1868
2031
|
const join = joins.find(j => j[1].alias === alias);
|
|
@@ -1877,7 +2040,7 @@ export class QueryBuilder {
|
|
|
1877
2040
|
for (const [key, join] of joins) {
|
|
1878
2041
|
const path = this.normalizeJoinPath(join, meta);
|
|
1879
2042
|
if (!populatePaths.has(path) && !orderByAliases.includes(join.alias)) {
|
|
1880
|
-
delete this.
|
|
2043
|
+
delete this.#state.joins[key];
|
|
1881
2044
|
}
|
|
1882
2045
|
}
|
|
1883
2046
|
}
|
|
@@ -1909,25 +2072,25 @@ export class QueryBuilder {
|
|
|
1909
2072
|
}
|
|
1910
2073
|
wrapModifySubQuery(meta) {
|
|
1911
2074
|
const subQuery = this.clone();
|
|
1912
|
-
subQuery.finalized = true;
|
|
2075
|
+
subQuery.#state.finalized = true;
|
|
1913
2076
|
// wrap one more time to get around MySQL limitations
|
|
1914
2077
|
// https://stackoverflow.com/questions/45494/mysql-error-1093-cant-specify-target-table-for-update-in-from-clause
|
|
1915
2078
|
const subSubQuery = this.platform.createNativeQueryBuilder();
|
|
1916
|
-
const method = this.flags.has(QueryFlag.UPDATE_SUB_QUERY) ? 'update' : 'delete';
|
|
2079
|
+
const method = this.#state.flags.has(QueryFlag.UPDATE_SUB_QUERY) ? 'update' : 'delete';
|
|
1917
2080
|
const schema = this.getSchema(this.mainAlias);
|
|
1918
2081
|
const pks = this.prepareFields(meta.primaryKeys, 'sub-query', schema);
|
|
1919
|
-
this.
|
|
1920
|
-
this.
|
|
2082
|
+
this.#state.cond = {}; // otherwise we would trigger validation error
|
|
2083
|
+
this.#state.joins = {}; // included in the subquery
|
|
1921
2084
|
subSubQuery.select(pks).from(subQuery.as(this.mainAlias.aliasName));
|
|
1922
2085
|
const { sql, params } = subSubQuery.compile();
|
|
1923
|
-
this[method](this.
|
|
2086
|
+
this[method](this.#state.data).where({
|
|
1924
2087
|
[Utils.getPrimaryKeyHash(meta.primaryKeys)]: { $in: raw(sql, params) },
|
|
1925
2088
|
});
|
|
1926
2089
|
}
|
|
1927
2090
|
getSchema(alias) {
|
|
1928
2091
|
const { meta } = alias;
|
|
1929
2092
|
const metaSchema = meta.schema && meta.schema !== '*' ? meta.schema : undefined;
|
|
1930
|
-
const schema = this.
|
|
2093
|
+
const schema = this.#state.schema ?? metaSchema ?? this.em?.schema ?? this.em?.config.getSchema(true);
|
|
1931
2094
|
if (schema === this.platform.getDefaultSchemaName()) {
|
|
1932
2095
|
return undefined;
|
|
1933
2096
|
}
|
|
@@ -1937,35 +2100,51 @@ export class QueryBuilder {
|
|
|
1937
2100
|
createAlias(entityName, aliasName, subQuery) {
|
|
1938
2101
|
const meta = this.metadata.find(entityName);
|
|
1939
2102
|
const alias = { aliasName, entityName, meta, subQuery };
|
|
1940
|
-
this.
|
|
2103
|
+
this.#state.aliases[aliasName] = alias;
|
|
1941
2104
|
return alias;
|
|
1942
2105
|
}
|
|
1943
2106
|
createMainAlias(entityName, aliasName, subQuery) {
|
|
1944
|
-
this.
|
|
1945
|
-
this
|
|
1946
|
-
return this.
|
|
2107
|
+
this.#state.mainAlias = this.createAlias(entityName, aliasName, subQuery);
|
|
2108
|
+
this.#helper = this.createQueryBuilderHelper();
|
|
2109
|
+
return this.#state.mainAlias;
|
|
1947
2110
|
}
|
|
1948
2111
|
fromSubQuery(target, aliasName) {
|
|
1949
|
-
const subQuery = target.getNativeQuery();
|
|
1950
2112
|
const { entityName } = target.mainAlias;
|
|
1951
2113
|
aliasName ??= this.getNextAlias(entityName);
|
|
2114
|
+
const subQuery = target.#state.unionQuery ? target.toRaw() : target.getNativeQuery();
|
|
1952
2115
|
this.createMainAlias(entityName, aliasName, subQuery);
|
|
1953
2116
|
}
|
|
1954
2117
|
fromEntityName(entityName, aliasName) {
|
|
1955
|
-
aliasName ??= this.
|
|
2118
|
+
aliasName ??= this.#state.mainAlias?.aliasName ?? this.getNextAlias(entityName);
|
|
1956
2119
|
this.createMainAlias(entityName, aliasName);
|
|
1957
2120
|
}
|
|
2121
|
+
fromRawTable(tableName, aliasName) {
|
|
2122
|
+
aliasName ??= this.#state.mainAlias?.aliasName ?? this.getNextAlias(tableName);
|
|
2123
|
+
const meta = new EntityMetadata({
|
|
2124
|
+
className: tableName,
|
|
2125
|
+
collection: tableName,
|
|
2126
|
+
});
|
|
2127
|
+
meta.root = meta;
|
|
2128
|
+
this.#state.mainAlias = {
|
|
2129
|
+
aliasName,
|
|
2130
|
+
entityName: tableName,
|
|
2131
|
+
meta,
|
|
2132
|
+
rawTableName: tableName,
|
|
2133
|
+
};
|
|
2134
|
+
this.#state.aliases[aliasName] = this.#state.mainAlias;
|
|
2135
|
+
this.#helper = this.createQueryBuilderHelper();
|
|
2136
|
+
}
|
|
1958
2137
|
createQueryBuilderHelper() {
|
|
1959
|
-
return new QueryBuilderHelper(this.mainAlias.entityName, this.mainAlias.aliasName, this.
|
|
2138
|
+
return new QueryBuilderHelper(this.mainAlias.entityName, this.mainAlias.aliasName, this.#state.aliases, this.#state.subQueries, this.driver, this.#state.tptAlias);
|
|
1960
2139
|
}
|
|
1961
2140
|
ensureFromClause() {
|
|
1962
2141
|
/* v8 ignore next */
|
|
1963
|
-
if (!this.
|
|
2142
|
+
if (!this.#state.mainAlias) {
|
|
1964
2143
|
throw new Error(`Cannot proceed to build a query because the main alias is not set.`);
|
|
1965
2144
|
}
|
|
1966
2145
|
}
|
|
1967
2146
|
ensureNotFinalized() {
|
|
1968
|
-
if (this.finalized) {
|
|
2147
|
+
if (this.#state.finalized) {
|
|
1969
2148
|
throw new Error('This QueryBuilder instance is already finalized, clone it first if you want to modify it.');
|
|
1970
2149
|
}
|
|
1971
2150
|
}
|
|
@@ -1974,28 +2153,35 @@ export class QueryBuilder {
|
|
|
1974
2153
|
[Symbol.for('nodejs.util.inspect.custom')](depth = 2) {
|
|
1975
2154
|
const object = { ...this };
|
|
1976
2155
|
const hidden = ['metadata', 'driver', 'context', 'platform', 'type'];
|
|
1977
|
-
Object.keys(object)
|
|
1978
|
-
|
|
2156
|
+
Object.keys(object)
|
|
2157
|
+
.filter(k => k.startsWith('_'))
|
|
2158
|
+
.forEach(k => delete object[k]);
|
|
2159
|
+
Object.keys(object)
|
|
2160
|
+
.filter(k => object[k] == null)
|
|
2161
|
+
.forEach(k => delete object[k]);
|
|
1979
2162
|
hidden.forEach(k => delete object[k]);
|
|
1980
2163
|
let prefix = this.type ? this.type.substring(0, 1) + this.type.toLowerCase().substring(1) : '';
|
|
1981
|
-
if (this.
|
|
1982
|
-
object.data = this.
|
|
2164
|
+
if (this.#state.data) {
|
|
2165
|
+
object.data = this.#state.data;
|
|
1983
2166
|
}
|
|
1984
|
-
if (this.
|
|
1985
|
-
object.schema = this.
|
|
2167
|
+
if (this.#state.schema) {
|
|
2168
|
+
object.schema = this.#state.schema;
|
|
1986
2169
|
}
|
|
1987
|
-
if (!Utils.isEmpty(this.
|
|
1988
|
-
object.where = this.
|
|
2170
|
+
if (!Utils.isEmpty(this.#state.cond)) {
|
|
2171
|
+
object.where = this.#state.cond;
|
|
1989
2172
|
}
|
|
1990
|
-
if (this.
|
|
2173
|
+
if (this.#state.onConflict?.[0]) {
|
|
1991
2174
|
prefix = 'Upsert';
|
|
1992
|
-
object.onConflict = this.
|
|
2175
|
+
object.onConflict = this.#state.onConflict[0];
|
|
1993
2176
|
}
|
|
1994
|
-
if (!Utils.isEmpty(this.
|
|
1995
|
-
object.orderBy = this.
|
|
2177
|
+
if (!Utils.isEmpty(this.#state.orderBy)) {
|
|
2178
|
+
object.orderBy = this.#state.orderBy;
|
|
1996
2179
|
}
|
|
1997
|
-
const name = this.
|
|
2180
|
+
const name = this.#state.mainAlias
|
|
2181
|
+
? `${prefix}QueryBuilder<${Utils.className(this.#state.mainAlias?.entityName)}>`
|
|
2182
|
+
: 'QueryBuilder';
|
|
1998
2183
|
const ret = inspect(object, { depth });
|
|
1999
2184
|
return ret === '[Object]' ? `[${name}]` : name + ' ' + ret;
|
|
2000
2185
|
}
|
|
2001
2186
|
}
|
|
2187
|
+
_a = QueryBuilder;
|