@mikro-orm/knex 6.0.0-dev.19 → 6.0.0-dev.191
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 +8 -4
- package/AbstractSqlConnection.js +30 -18
- package/AbstractSqlDriver.d.ts +21 -23
- package/AbstractSqlDriver.js +390 -225
- package/AbstractSqlPlatform.d.ts +5 -4
- package/AbstractSqlPlatform.js +34 -5
- package/MonkeyPatchable.d.ts +1 -0
- package/MonkeyPatchable.js +3 -0
- package/PivotCollectionPersister.d.ts +17 -0
- package/PivotCollectionPersister.js +128 -0
- package/README.md +64 -107
- package/SqlEntityManager.d.ts +2 -6
- package/SqlEntityManager.js +4 -8
- package/SqlEntityRepository.d.ts +7 -6
- package/SqlEntityRepository.js +11 -8
- package/index.mjs +24 -5
- package/package.json +8 -8
- package/query/ArrayCriteriaNode.d.ts +4 -3
- package/query/ArrayCriteriaNode.js +5 -0
- package/query/CriteriaNode.d.ts +10 -8
- package/query/CriteriaNode.js +23 -10
- package/query/CriteriaNodeFactory.d.ts +6 -6
- package/query/CriteriaNodeFactory.js +28 -14
- package/query/ObjectCriteriaNode.d.ts +4 -3
- package/query/ObjectCriteriaNode.js +51 -18
- package/query/QueryBuilder.d.ts +50 -24
- package/query/QueryBuilder.js +401 -133
- package/query/QueryBuilderHelper.d.ts +11 -16
- package/query/QueryBuilderHelper.js +194 -155
- package/query/ScalarCriteriaNode.d.ts +3 -2
- package/query/ScalarCriteriaNode.js +10 -6
- package/query/enums.d.ts +7 -0
- package/query/enums.js +10 -2
- package/schema/DatabaseSchema.d.ts +5 -2
- package/schema/DatabaseSchema.js +28 -10
- package/schema/DatabaseTable.d.ts +13 -5
- package/schema/DatabaseTable.js +385 -60
- package/schema/SchemaComparator.d.ts +5 -5
- package/schema/SchemaComparator.js +59 -35
- package/schema/SchemaHelper.d.ts +7 -5
- package/schema/SchemaHelper.js +25 -11
- package/schema/SqlSchemaGenerator.d.ts +3 -6
- package/schema/SqlSchemaGenerator.js +71 -36
- package/typings.d.ts +22 -10
package/query/QueryBuilder.js
CHANGED
|
@@ -26,6 +26,12 @@ const CriteriaNodeFactory_1 = require("./CriteriaNodeFactory");
|
|
|
26
26
|
* ```
|
|
27
27
|
*/
|
|
28
28
|
class QueryBuilder {
|
|
29
|
+
metadata;
|
|
30
|
+
driver;
|
|
31
|
+
context;
|
|
32
|
+
connectionType;
|
|
33
|
+
em;
|
|
34
|
+
logging;
|
|
29
35
|
get mainAlias() {
|
|
30
36
|
this.ensureFromClause();
|
|
31
37
|
return this._mainAlias;
|
|
@@ -37,31 +43,56 @@ class QueryBuilder {
|
|
|
37
43
|
this.ensureFromClause();
|
|
38
44
|
return this._helper;
|
|
39
45
|
}
|
|
46
|
+
/** @internal */
|
|
47
|
+
type;
|
|
48
|
+
/** @internal */
|
|
49
|
+
_fields;
|
|
50
|
+
/** @internal */
|
|
51
|
+
_populate = [];
|
|
52
|
+
/** @internal */
|
|
53
|
+
_populateWhere;
|
|
54
|
+
/** @internal */
|
|
55
|
+
_populateMap = {};
|
|
56
|
+
aliasCounter = 0;
|
|
57
|
+
flags = new Set([core_1.QueryFlag.CONVERT_CUSTOM_TYPES]);
|
|
58
|
+
finalized = false;
|
|
59
|
+
_joins = {};
|
|
60
|
+
_explicitAlias = false;
|
|
61
|
+
_schema;
|
|
62
|
+
_cond = {};
|
|
63
|
+
_data;
|
|
64
|
+
_orderBy = [];
|
|
65
|
+
_groupBy = [];
|
|
66
|
+
_having = {};
|
|
67
|
+
_returning;
|
|
68
|
+
_onConflict;
|
|
69
|
+
_limit;
|
|
70
|
+
_offset;
|
|
71
|
+
_distinctOn;
|
|
72
|
+
_joinedProps = new Map();
|
|
73
|
+
_cache;
|
|
74
|
+
_indexHint;
|
|
75
|
+
_comments = [];
|
|
76
|
+
_hintComments = [];
|
|
77
|
+
flushMode;
|
|
78
|
+
lockMode;
|
|
79
|
+
lockTables;
|
|
80
|
+
subQueries = {};
|
|
81
|
+
_mainAlias;
|
|
82
|
+
_aliases = {};
|
|
83
|
+
_helper;
|
|
84
|
+
platform;
|
|
85
|
+
knex;
|
|
40
86
|
/**
|
|
41
87
|
* @internal
|
|
42
88
|
*/
|
|
43
|
-
constructor(entityName, metadata, driver, context, alias, connectionType, em) {
|
|
89
|
+
constructor(entityName, metadata, driver, context, alias, connectionType, em, logging) {
|
|
44
90
|
this.metadata = metadata;
|
|
45
91
|
this.driver = driver;
|
|
46
92
|
this.context = context;
|
|
47
93
|
this.connectionType = connectionType;
|
|
48
94
|
this.em = em;
|
|
49
|
-
|
|
50
|
-
this._populate = [];
|
|
51
|
-
/** @internal */
|
|
52
|
-
this._populateMap = {};
|
|
53
|
-
this.aliasCounter = 0;
|
|
54
|
-
this.flags = new Set([core_1.QueryFlag.CONVERT_CUSTOM_TYPES]);
|
|
55
|
-
this.finalized = false;
|
|
56
|
-
this._joins = {};
|
|
57
|
-
this._explicitAlias = false;
|
|
58
|
-
this._cond = {};
|
|
59
|
-
this._orderBy = [];
|
|
60
|
-
this._groupBy = [];
|
|
61
|
-
this._having = {};
|
|
62
|
-
this._joinedProps = new Map();
|
|
63
|
-
this.subQueries = {};
|
|
64
|
-
this._aliases = {};
|
|
95
|
+
this.logging = logging;
|
|
65
96
|
this.platform = this.driver.getPlatform();
|
|
66
97
|
this.knex = this.driver.getConnection(this.connectionType).getKnex();
|
|
67
98
|
if (alias) {
|
|
@@ -116,27 +147,45 @@ class QueryBuilder {
|
|
|
116
147
|
this._fields = this.mainAlias.metadata.primaryKeys;
|
|
117
148
|
}
|
|
118
149
|
else {
|
|
119
|
-
this._fields = [
|
|
150
|
+
this._fields = [(0, core_1.raw)('*')];
|
|
120
151
|
}
|
|
121
152
|
if (distinct) {
|
|
122
153
|
this.flags.add(core_1.QueryFlag.DISTINCT);
|
|
123
154
|
}
|
|
124
155
|
return this.init(enums_1.QueryType.COUNT);
|
|
125
156
|
}
|
|
126
|
-
join(field, alias, cond = {}, type =
|
|
127
|
-
this.joinReference(field, alias, cond, type, path);
|
|
157
|
+
join(field, alias, cond = {}, type = enums_1.JoinType.innerJoin, path, schema) {
|
|
158
|
+
this.joinReference(field, alias, cond, type, path, schema);
|
|
159
|
+
return this;
|
|
160
|
+
}
|
|
161
|
+
innerJoin(field, alias, cond = {}, schema) {
|
|
162
|
+
this.join(field, alias, cond, enums_1.JoinType.innerJoin, undefined, schema);
|
|
128
163
|
return this;
|
|
129
164
|
}
|
|
130
|
-
|
|
131
|
-
|
|
165
|
+
innerJoinLateral(field, alias, cond = {}, schema) {
|
|
166
|
+
this.join(field, alias, cond, enums_1.JoinType.innerJoinLateral, undefined, schema);
|
|
167
|
+
return this;
|
|
168
|
+
}
|
|
169
|
+
leftJoin(field, alias, cond = {}, schema) {
|
|
170
|
+
return this.join(field, alias, cond, enums_1.JoinType.leftJoin, undefined, schema);
|
|
171
|
+
}
|
|
172
|
+
leftJoinLateral(field, alias, cond = {}, schema) {
|
|
173
|
+
return this.join(field, alias, cond, enums_1.JoinType.leftJoinLateral, undefined, schema);
|
|
132
174
|
}
|
|
133
|
-
joinAndSelect(field, alias, cond = {}, type =
|
|
175
|
+
joinAndSelect(field, alias, cond = {}, type = enums_1.JoinType.innerJoin, path, fields, schema) {
|
|
134
176
|
if (!this.type) {
|
|
135
177
|
this.select('*');
|
|
136
178
|
}
|
|
137
|
-
|
|
138
|
-
|
|
179
|
+
let subquery;
|
|
180
|
+
if (Array.isArray(field)) {
|
|
181
|
+
subquery = field[1] instanceof QueryBuilder ? field[1].getFormattedQuery() : field[1].toString();
|
|
182
|
+
field = field[0];
|
|
183
|
+
}
|
|
184
|
+
const prop = this.joinReference(field, alias, cond, type, path, schema, subquery);
|
|
139
185
|
const [fromAlias] = this.helper.splitField(field);
|
|
186
|
+
if (subquery) {
|
|
187
|
+
this._joins[`${fromAlias}.${prop.name}#${alias}`].subquery = subquery;
|
|
188
|
+
}
|
|
140
189
|
const populate = this._joinedProps.get(fromAlias);
|
|
141
190
|
const item = { field: prop.name, strategy: core_1.LoadStrategy.JOINED, children: [] };
|
|
142
191
|
if (populate) {
|
|
@@ -146,15 +195,39 @@ class QueryBuilder {
|
|
|
146
195
|
this._populate.push(item);
|
|
147
196
|
}
|
|
148
197
|
this._joinedProps.set(alias, item);
|
|
198
|
+
this.addSelect(this.getFieldsForJoinedLoad(prop, alias, fields));
|
|
149
199
|
return this;
|
|
150
200
|
}
|
|
151
|
-
leftJoinAndSelect(field, alias, cond = {}) {
|
|
152
|
-
return this.joinAndSelect(field, alias, cond,
|
|
201
|
+
leftJoinAndSelect(field, alias, cond = {}, fields, schema) {
|
|
202
|
+
return this.joinAndSelect(field, alias, cond, enums_1.JoinType.leftJoin, undefined, fields, schema);
|
|
203
|
+
}
|
|
204
|
+
leftJoinLateralAndSelect(field, alias, cond = {}, fields, schema) {
|
|
205
|
+
return this.joinAndSelect(field, alias, cond, enums_1.JoinType.leftJoinLateral, undefined, fields, schema);
|
|
153
206
|
}
|
|
154
|
-
|
|
207
|
+
innerJoinAndSelect(field, alias, cond = {}, fields, schema) {
|
|
208
|
+
return this.joinAndSelect(field, alias, cond, enums_1.JoinType.innerJoin, undefined, fields, schema);
|
|
209
|
+
}
|
|
210
|
+
innerJoinLateralAndSelect(field, alias, cond = {}, fields, schema) {
|
|
211
|
+
return this.joinAndSelect(field, alias, cond, enums_1.JoinType.innerJoinLateral, undefined, fields, schema);
|
|
212
|
+
}
|
|
213
|
+
getFieldsForJoinedLoad(prop, alias, explicitFields) {
|
|
155
214
|
const fields = [];
|
|
215
|
+
const populate = [];
|
|
216
|
+
const joinKey = Object.keys(this._joins).find(join => join.endsWith(`#${alias}`));
|
|
217
|
+
if (joinKey) {
|
|
218
|
+
const path = this._joins[joinKey].path.split('.').slice(1);
|
|
219
|
+
let children = this._populate;
|
|
220
|
+
for (let i = 0; i < path.length; i++) {
|
|
221
|
+
const child = children.filter(hint => hint.field === path[i]);
|
|
222
|
+
if (child.length === 0) {
|
|
223
|
+
break;
|
|
224
|
+
}
|
|
225
|
+
children = child.flatMap(c => c.children);
|
|
226
|
+
}
|
|
227
|
+
populate.push(...children);
|
|
228
|
+
}
|
|
156
229
|
prop.targetMeta.props
|
|
157
|
-
.filter(prop => this.platform.shouldHaveColumn(prop,
|
|
230
|
+
.filter(prop => explicitFields ? explicitFields.includes(prop.name) || prop.primary : this.platform.shouldHaveColumn(prop, populate))
|
|
158
231
|
.forEach(prop => fields.push(...this.driver.mapPropToFieldNames(this, prop, alias)));
|
|
159
232
|
return fields;
|
|
160
233
|
}
|
|
@@ -165,9 +238,15 @@ class QueryBuilder {
|
|
|
165
238
|
}
|
|
166
239
|
where(cond, params, operator) {
|
|
167
240
|
this.ensureNotFinalized();
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
241
|
+
const rawField = core_1.RawQueryFragment.getKnownFragment(cond);
|
|
242
|
+
if (rawField) {
|
|
243
|
+
const sql = this.platform.formatQuery(rawField.sql, rawField.params);
|
|
244
|
+
cond = { [(0, core_1.raw)(`(${sql})`)]: core_1.Utils.asArray(params) };
|
|
245
|
+
operator ??= '$and';
|
|
246
|
+
}
|
|
247
|
+
else if (core_1.Utils.isString(cond)) {
|
|
248
|
+
cond = { [(0, core_1.raw)(`(${cond})`, core_1.Utils.asArray(params))]: [] };
|
|
249
|
+
operator ??= '$and';
|
|
171
250
|
}
|
|
172
251
|
else {
|
|
173
252
|
cond = core_1.QueryHelper.processWhere({
|
|
@@ -222,6 +301,7 @@ class QueryBuilder {
|
|
|
222
301
|
aliasMap: this.getAliasMap(),
|
|
223
302
|
aliased: !this.type || [enums_1.QueryType.SELECT, enums_1.QueryType.COUNT].includes(this.type),
|
|
224
303
|
convertCustomTypes: false,
|
|
304
|
+
type: 'orderBy',
|
|
225
305
|
});
|
|
226
306
|
this._orderBy.push(CriteriaNodeFactory_1.CriteriaNodeFactory.createNode(this.metadata, this.mainAlias.entityName, processed).process(this));
|
|
227
307
|
});
|
|
@@ -235,15 +315,22 @@ class QueryBuilder {
|
|
|
235
315
|
having(cond = {}, params) {
|
|
236
316
|
this.ensureNotFinalized();
|
|
237
317
|
if (core_1.Utils.isString(cond)) {
|
|
238
|
-
cond = { [`(${cond})
|
|
318
|
+
cond = { [(0, core_1.raw)(`(${cond})`, params)]: [] };
|
|
239
319
|
}
|
|
240
320
|
this._having = CriteriaNodeFactory_1.CriteriaNodeFactory.createNode(this.metadata, this.mainAlias.entityName, cond).process(this);
|
|
241
321
|
return this;
|
|
242
322
|
}
|
|
243
323
|
onConflict(fields = []) {
|
|
324
|
+
const meta = this.mainAlias.metadata;
|
|
244
325
|
this.ensureNotFinalized();
|
|
245
|
-
this._onConflict
|
|
246
|
-
this._onConflict.push({
|
|
326
|
+
this._onConflict ??= [];
|
|
327
|
+
this._onConflict.push({
|
|
328
|
+
fields: core_1.Utils.asArray(fields).flatMap(f => {
|
|
329
|
+
const key = f.toString();
|
|
330
|
+
/* istanbul ignore next */
|
|
331
|
+
return meta.properties[key]?.fieldNames ?? [key];
|
|
332
|
+
}),
|
|
333
|
+
});
|
|
247
334
|
return this;
|
|
248
335
|
}
|
|
249
336
|
ignore() {
|
|
@@ -263,6 +350,10 @@ class QueryBuilder {
|
|
|
263
350
|
this._onConflict[this._onConflict.length - 1].merge = data;
|
|
264
351
|
return this;
|
|
265
352
|
}
|
|
353
|
+
returning(fields) {
|
|
354
|
+
this._returning = core_1.Utils.asArray(fields);
|
|
355
|
+
return this;
|
|
356
|
+
}
|
|
266
357
|
/**
|
|
267
358
|
* @internal
|
|
268
359
|
*/
|
|
@@ -272,17 +363,6 @@ class QueryBuilder {
|
|
|
272
363
|
this._populateWhere = populateWhere;
|
|
273
364
|
return this;
|
|
274
365
|
}
|
|
275
|
-
/**
|
|
276
|
-
* @internal
|
|
277
|
-
*/
|
|
278
|
-
ref(field) {
|
|
279
|
-
return this.knex.ref(field);
|
|
280
|
-
}
|
|
281
|
-
raw(sql, bindings = []) {
|
|
282
|
-
const raw = this.knex.raw(sql, bindings);
|
|
283
|
-
raw.__raw = true; // tag it as there is now way to check via `instanceof`
|
|
284
|
-
return raw;
|
|
285
|
-
}
|
|
286
366
|
limit(limit, offset = 0) {
|
|
287
367
|
this.ensureNotFinalized();
|
|
288
368
|
this._limit = limit;
|
|
@@ -325,6 +405,9 @@ class QueryBuilder {
|
|
|
325
405
|
this.flags.delete(flag);
|
|
326
406
|
return this;
|
|
327
407
|
}
|
|
408
|
+
hasFlag(flag) {
|
|
409
|
+
return this.flags.has(flag);
|
|
410
|
+
}
|
|
328
411
|
cache(config = true) {
|
|
329
412
|
this.ensureNotFinalized();
|
|
330
413
|
this._cache = config;
|
|
@@ -338,6 +421,24 @@ class QueryBuilder {
|
|
|
338
421
|
this._indexHint = sql;
|
|
339
422
|
return this;
|
|
340
423
|
}
|
|
424
|
+
/**
|
|
425
|
+
* Prepend comment to the sql query using the syntax `/* ... *‍/`. Some characters are forbidden such as `/*, *‍/` and `?`.
|
|
426
|
+
*/
|
|
427
|
+
comment(comment) {
|
|
428
|
+
this.ensureNotFinalized();
|
|
429
|
+
this._comments.push(...core_1.Utils.asArray(comment));
|
|
430
|
+
return this;
|
|
431
|
+
}
|
|
432
|
+
/**
|
|
433
|
+
* Add hints to the query using comment-like syntax `/*+ ... *‍/`. MySQL and Oracle use this syntax for optimizer hints.
|
|
434
|
+
* Also various DB proxies and routers use this syntax to pass hints to alter their behavior. In other dialects the hints
|
|
435
|
+
* are ignored as simple comments.
|
|
436
|
+
*/
|
|
437
|
+
hintComment(comment) {
|
|
438
|
+
this.ensureNotFinalized();
|
|
439
|
+
this._hintComments.push(...core_1.Utils.asArray(comment));
|
|
440
|
+
return this;
|
|
441
|
+
}
|
|
341
442
|
from(target, aliasName) {
|
|
342
443
|
this.ensureNotFinalized();
|
|
343
444
|
if (target instanceof QueryBuilder) {
|
|
@@ -355,17 +456,21 @@ class QueryBuilder {
|
|
|
355
456
|
getKnexQuery() {
|
|
356
457
|
this.finalize();
|
|
357
458
|
const qb = this.getQueryBase();
|
|
459
|
+
qb.__raw = true; // tag it as there is now way to check via `instanceof`
|
|
358
460
|
core_1.Utils.runIfNotEmpty(() => this.helper.appendQueryCondition(this.type ?? enums_1.QueryType.SELECT, this._cond, qb), this._cond && !this._onConflict);
|
|
359
461
|
core_1.Utils.runIfNotEmpty(() => qb.groupBy(this.prepareFields(this._groupBy, 'groupBy')), this._groupBy);
|
|
360
462
|
core_1.Utils.runIfNotEmpty(() => this.helper.appendQueryCondition(this.type ?? enums_1.QueryType.SELECT, this._having, qb, undefined, 'having'), this._having);
|
|
361
463
|
core_1.Utils.runIfNotEmpty(() => {
|
|
362
464
|
const queryOrder = this.helper.getQueryOrder(this.type ?? enums_1.QueryType.SELECT, this._orderBy, this._populateMap);
|
|
363
465
|
if (queryOrder) {
|
|
364
|
-
|
|
466
|
+
qb.orderByRaw(queryOrder);
|
|
467
|
+
return;
|
|
365
468
|
}
|
|
366
469
|
}, this._orderBy);
|
|
367
470
|
core_1.Utils.runIfNotEmpty(() => qb.limit(this._limit), this._limit != null);
|
|
368
471
|
core_1.Utils.runIfNotEmpty(() => qb.offset(this._offset), this._offset);
|
|
472
|
+
core_1.Utils.runIfNotEmpty(() => this._comments.forEach(comment => qb.comment(comment)), this._comments);
|
|
473
|
+
core_1.Utils.runIfNotEmpty(() => this._hintComments.forEach(comment => qb.hintComment(comment)), this._hintComments);
|
|
369
474
|
core_1.Utils.runIfNotEmpty(() => this.helper.appendOnConflictClause(this.type ?? enums_1.QueryType.SELECT, this._onConflict, qb), this._onConflict);
|
|
370
475
|
if (this.type === enums_1.QueryType.TRUNCATE && this.platform.usesCascadeStatement()) {
|
|
371
476
|
return this.knex.raw(qb.toSQL().toNative().sql + ' cascade');
|
|
@@ -373,26 +478,35 @@ class QueryBuilder {
|
|
|
373
478
|
if (this.lockMode) {
|
|
374
479
|
this.helper.getLockSQL(qb, this.lockMode, this.lockTables);
|
|
375
480
|
}
|
|
376
|
-
this.helper.finalize(this.type ?? enums_1.QueryType.SELECT, qb, this.mainAlias.metadata);
|
|
481
|
+
this.helper.finalize(this.type ?? enums_1.QueryType.SELECT, qb, this.mainAlias.metadata, this._data, this._returning);
|
|
377
482
|
return qb;
|
|
378
483
|
}
|
|
379
484
|
/**
|
|
380
485
|
* Returns the query with parameters as wildcards.
|
|
381
486
|
*/
|
|
382
487
|
getQuery() {
|
|
383
|
-
return this.
|
|
488
|
+
return this.toQuery().sql;
|
|
489
|
+
}
|
|
490
|
+
#query;
|
|
491
|
+
toQuery() {
|
|
492
|
+
if (this.#query) {
|
|
493
|
+
return this.#query;
|
|
494
|
+
}
|
|
495
|
+
const sql = this.getKnexQuery().toSQL();
|
|
496
|
+
const query = sql.toNative();
|
|
497
|
+
return this.#query = { sql: query.sql, params: query.bindings ?? [], _sql: sql };
|
|
384
498
|
}
|
|
385
499
|
/**
|
|
386
500
|
* Returns the list of all parameters for this query.
|
|
387
501
|
*/
|
|
388
502
|
getParams() {
|
|
389
|
-
return this.
|
|
503
|
+
return this.toQuery().params;
|
|
390
504
|
}
|
|
391
505
|
/**
|
|
392
506
|
* Returns raw interpolated query string with all the parameters inlined.
|
|
393
507
|
*/
|
|
394
508
|
getFormattedQuery() {
|
|
395
|
-
const query = this.
|
|
509
|
+
const query = this.toQuery()._sql;
|
|
396
510
|
return this.platform.formatQuery(query.sql, query.bindings);
|
|
397
511
|
}
|
|
398
512
|
/**
|
|
@@ -424,7 +538,10 @@ class QueryBuilder {
|
|
|
424
538
|
* Executes this QB and returns the raw results, mapped to the property names (unless disabled via last parameter).
|
|
425
539
|
* Use `method` to specify what kind of result you want to get (array/single/meta).
|
|
426
540
|
*/
|
|
427
|
-
async execute(method = 'all',
|
|
541
|
+
async execute(method = 'all', options) {
|
|
542
|
+
options = typeof options === 'boolean' ? { mapResults: options } : (options ?? {});
|
|
543
|
+
options.mergeResults ??= true;
|
|
544
|
+
options.mapResults ??= true;
|
|
428
545
|
if (!this.connectionType && method !== 'run' && [enums_1.QueryType.INSERT, enums_1.QueryType.UPDATE, enums_1.QueryType.DELETE, enums_1.QueryType.TRUNCATE].includes(this.type ?? enums_1.QueryType.SELECT)) {
|
|
429
546
|
this.connectionType = 'write';
|
|
430
547
|
}
|
|
@@ -433,21 +550,33 @@ class QueryBuilder {
|
|
|
433
550
|
if (cached?.data) {
|
|
434
551
|
return cached.data;
|
|
435
552
|
}
|
|
436
|
-
const
|
|
437
|
-
const
|
|
553
|
+
const write = method === 'run' || !this.platform.getConfig().get('preferReadReplicas');
|
|
554
|
+
const type = this.connectionType || (write ? 'write' : 'read');
|
|
555
|
+
const res = await this.driver.getConnection(type).execute(query.sql, query.bindings, method, this.context, this.logging);
|
|
438
556
|
const meta = this.mainAlias.metadata;
|
|
439
|
-
if (!mapResults || !meta) {
|
|
557
|
+
if (!options.mapResults || !meta) {
|
|
440
558
|
await this.em?.storeCache(this._cache, cached, res);
|
|
441
559
|
return res;
|
|
442
560
|
}
|
|
443
|
-
if (method === '
|
|
561
|
+
if (method === 'run') {
|
|
562
|
+
return res;
|
|
563
|
+
}
|
|
564
|
+
const joinedProps = this.driver.joinedProps(meta, this._populate);
|
|
565
|
+
let mapped;
|
|
566
|
+
if (Array.isArray(res)) {
|
|
444
567
|
const map = {};
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
568
|
+
mapped = res.map(r => this.driver.mapResult(r, meta, this._populate, this, map));
|
|
569
|
+
if (options.mergeResults && joinedProps.length > 0) {
|
|
570
|
+
mapped = this.driver.mergeJoinedResult(mapped, this.mainAlias.metadata, joinedProps);
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
else {
|
|
574
|
+
mapped = [this.driver.mapResult(res, meta, joinedProps, this)];
|
|
448
575
|
}
|
|
449
|
-
const mapped = this.driver.mapResult(res, meta, this._populate, this);
|
|
450
576
|
await this.em?.storeCache(this._cache, cached, mapped);
|
|
577
|
+
if (method === 'get') {
|
|
578
|
+
return mapped[0];
|
|
579
|
+
}
|
|
451
580
|
return mapped;
|
|
452
581
|
}
|
|
453
582
|
/**
|
|
@@ -461,11 +590,28 @@ class QueryBuilder {
|
|
|
461
590
|
*/
|
|
462
591
|
async getResultList() {
|
|
463
592
|
await this.em.tryFlush(this.mainAlias.entityName, { flushMode: this.flushMode });
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
593
|
+
const res = await this.execute('all', true);
|
|
594
|
+
const entities = [];
|
|
595
|
+
function propagatePopulateHint(entity, hint) {
|
|
596
|
+
(0, core_1.helper)(entity).__serializationContext.populate ??= hint;
|
|
597
|
+
hint.forEach(pop => {
|
|
598
|
+
const value = entity[pop.field];
|
|
599
|
+
if (core_1.Utils.isEntity(value, true)) {
|
|
600
|
+
(0, core_1.helper)(value).populated();
|
|
601
|
+
propagatePopulateHint(value, pop.children ?? []);
|
|
602
|
+
}
|
|
603
|
+
else if (core_1.Utils.isCollection(value)) {
|
|
604
|
+
value.populated();
|
|
605
|
+
value.getItems(false).forEach(item => propagatePopulateHint(item, pop.children ?? []));
|
|
606
|
+
}
|
|
607
|
+
});
|
|
467
608
|
}
|
|
468
|
-
|
|
609
|
+
for (const r of res) {
|
|
610
|
+
const entity = this.em.map(this.mainAlias.entityName, r, { schema: this._schema });
|
|
611
|
+
propagatePopulateHint(entity, this._populate);
|
|
612
|
+
entities.push(entity);
|
|
613
|
+
}
|
|
614
|
+
return entities;
|
|
469
615
|
}
|
|
470
616
|
/**
|
|
471
617
|
* Executes the query, returning the first result or null
|
|
@@ -483,7 +629,7 @@ class QueryBuilder {
|
|
|
483
629
|
res = await this.execute('get', false);
|
|
484
630
|
}
|
|
485
631
|
else {
|
|
486
|
-
const qb = this.clone();
|
|
632
|
+
const qb = this.type === undefined ? this : this.clone();
|
|
487
633
|
qb.count(field, distinct ?? qb.hasToManyJoins()).limit(undefined).offset(undefined).orderBy([]);
|
|
488
634
|
res = await qb.execute('get', false);
|
|
489
635
|
}
|
|
@@ -502,7 +648,11 @@ class QueryBuilder {
|
|
|
502
648
|
* Provides promise-like interface so we can await the QB instance.
|
|
503
649
|
*/
|
|
504
650
|
then(onfulfilled, onrejected) {
|
|
505
|
-
|
|
651
|
+
let type = this.type ?? enums_1.QueryType.SELECT;
|
|
652
|
+
if (this.flags.has(core_1.QueryFlag.UPDATE_SUB_QUERY) || this.flags.has(core_1.QueryFlag.DELETE_SUB_QUERY)) {
|
|
653
|
+
type = enums_1.QueryType.UPDATE;
|
|
654
|
+
}
|
|
655
|
+
switch (type) {
|
|
506
656
|
case enums_1.QueryType.INSERT:
|
|
507
657
|
case enums_1.QueryType.UPDATE:
|
|
508
658
|
case enums_1.QueryType.DELETE:
|
|
@@ -525,15 +675,22 @@ class QueryBuilder {
|
|
|
525
675
|
/* istanbul ignore next */
|
|
526
676
|
alias = meta?.properties[f]?.fieldNames[0] ?? alias;
|
|
527
677
|
}
|
|
528
|
-
|
|
678
|
+
const ret = qb.as(alias);
|
|
679
|
+
// tag the instance, so it is possible to detect it easily
|
|
680
|
+
Object.defineProperty(ret, '__as', { enumerable: false, value: alias });
|
|
681
|
+
return ret;
|
|
529
682
|
}
|
|
530
|
-
clone() {
|
|
683
|
+
clone(reset) {
|
|
531
684
|
const qb = new QueryBuilder(this.mainAlias.entityName, this.metadata, this.driver, this.context, this.mainAlias.aliasName, this.connectionType, this.em);
|
|
685
|
+
if (reset) {
|
|
686
|
+
return qb;
|
|
687
|
+
}
|
|
532
688
|
Object.assign(qb, this);
|
|
533
689
|
// clone array/object properties
|
|
534
690
|
const properties = [
|
|
535
691
|
'flags', '_populate', '_populateWhere', '_populateMap', '_joins', '_joinedProps', '_cond', '_data', '_orderBy',
|
|
536
|
-
'_schema', '_indexHint', '_cache', 'subQueries', 'lockMode', 'lockTables',
|
|
692
|
+
'_schema', '_indexHint', '_cache', 'subQueries', 'lockMode', 'lockTables', '_groupBy', '_having', '_returning',
|
|
693
|
+
'_comments', '_hintComments',
|
|
537
694
|
];
|
|
538
695
|
properties.forEach(prop => qb[prop] = core_1.Utils.copy(this[prop]));
|
|
539
696
|
/* istanbul ignore else */
|
|
@@ -561,14 +718,42 @@ class QueryBuilder {
|
|
|
561
718
|
}
|
|
562
719
|
return qb;
|
|
563
720
|
}
|
|
564
|
-
joinReference(field, alias, cond, type, path) {
|
|
721
|
+
joinReference(field, alias, cond, type, path, schema, subquery) {
|
|
565
722
|
this.ensureNotFinalized();
|
|
723
|
+
if (typeof field === 'object') {
|
|
724
|
+
const prop = {
|
|
725
|
+
name: '__subquery__',
|
|
726
|
+
kind: core_1.ReferenceKind.MANY_TO_ONE,
|
|
727
|
+
};
|
|
728
|
+
if (field instanceof QueryBuilder) {
|
|
729
|
+
prop.type = field.mainAlias.entityName;
|
|
730
|
+
prop.targetMeta = field.mainAlias.metadata;
|
|
731
|
+
field = field.getKnexQuery();
|
|
732
|
+
}
|
|
733
|
+
this._joins[`${this.alias}.${prop.name}#${alias}`] = {
|
|
734
|
+
prop,
|
|
735
|
+
alias,
|
|
736
|
+
type,
|
|
737
|
+
cond,
|
|
738
|
+
schema,
|
|
739
|
+
subquery: field.toString(),
|
|
740
|
+
ownerAlias: this.alias,
|
|
741
|
+
};
|
|
742
|
+
return prop;
|
|
743
|
+
}
|
|
744
|
+
if (!subquery && type.includes('lateral')) {
|
|
745
|
+
throw new Error(`Lateral join can be used only with a sub-query.`);
|
|
746
|
+
}
|
|
566
747
|
const [fromAlias, fromField] = this.helper.splitField(field);
|
|
567
|
-
const
|
|
748
|
+
const q = (str) => `'${str}'`;
|
|
749
|
+
if (!this._aliases[fromAlias]) {
|
|
750
|
+
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._aliases).map(q).join(', ')}.`);
|
|
751
|
+
}
|
|
752
|
+
const entityName = this._aliases[fromAlias].entityName;
|
|
568
753
|
const meta = this.metadata.get(entityName);
|
|
569
754
|
const prop = meta.properties[fromField];
|
|
570
755
|
if (!prop) {
|
|
571
|
-
throw new Error(`Trying to join ${field}, but ${fromField} is not a defined relation on ${meta.className}
|
|
756
|
+
throw new Error(`Trying to join ${q(field)}, but ${q(fromField)} is not a defined relation on ${meta.className}.`);
|
|
572
757
|
}
|
|
573
758
|
this.createAlias(prop.type, alias);
|
|
574
759
|
cond = core_1.QueryHelper.processWhere({
|
|
@@ -580,26 +765,26 @@ class QueryBuilder {
|
|
|
580
765
|
aliased: !this.type || [enums_1.QueryType.SELECT, enums_1.QueryType.COUNT].includes(this.type),
|
|
581
766
|
});
|
|
582
767
|
let aliasedName = `${fromAlias}.${prop.name}#${alias}`;
|
|
583
|
-
path
|
|
584
|
-
if (prop.
|
|
585
|
-
this._joins[aliasedName] = this.helper.joinOneToReference(prop, fromAlias, alias, type, cond);
|
|
768
|
+
path ??= `${(Object.values(this._joins).find(j => j.alias === fromAlias)?.path ?? entityName)}.${prop.name}`;
|
|
769
|
+
if (prop.kind === core_1.ReferenceKind.ONE_TO_MANY) {
|
|
770
|
+
this._joins[aliasedName] = this.helper.joinOneToReference(prop, fromAlias, alias, type, cond, schema);
|
|
586
771
|
}
|
|
587
|
-
else if (prop.
|
|
772
|
+
else if (prop.kind === core_1.ReferenceKind.MANY_TO_MANY) {
|
|
588
773
|
let pivotAlias = alias;
|
|
589
|
-
if (type !==
|
|
774
|
+
if (type !== enums_1.JoinType.pivotJoin) {
|
|
590
775
|
const oldPivotAlias = this.getAliasForJoinPath(path + '[pivot]');
|
|
591
776
|
pivotAlias = oldPivotAlias ?? this.getNextAlias(prop.pivotEntity);
|
|
592
777
|
aliasedName = `${fromAlias}.${prop.name}#${pivotAlias}`;
|
|
593
778
|
}
|
|
594
|
-
const joins = this.helper.joinManyToManyReference(prop, fromAlias, alias, pivotAlias, type, cond, path);
|
|
779
|
+
const joins = this.helper.joinManyToManyReference(prop, fromAlias, alias, pivotAlias, type, cond, path, schema);
|
|
595
780
|
Object.assign(this._joins, joins);
|
|
596
781
|
this.createAlias(prop.pivotEntity, pivotAlias);
|
|
597
782
|
}
|
|
598
|
-
else if (prop.
|
|
599
|
-
this._joins[aliasedName] = this.helper.joinOneToReference(prop, fromAlias, alias, type, cond);
|
|
783
|
+
else if (prop.kind === core_1.ReferenceKind.ONE_TO_ONE) {
|
|
784
|
+
this._joins[aliasedName] = this.helper.joinOneToReference(prop, fromAlias, alias, type, cond, schema);
|
|
600
785
|
}
|
|
601
786
|
else { // MANY_TO_ONE
|
|
602
|
-
this._joins[aliasedName] = this.helper.joinManyToOneReference(prop, fromAlias, alias, type, cond);
|
|
787
|
+
this._joins[aliasedName] = this.helper.joinManyToOneReference(prop, fromAlias, alias, type, cond, schema);
|
|
603
788
|
}
|
|
604
789
|
if (!this._joins[aliasedName].path && path) {
|
|
605
790
|
this._joins[aliasedName].path = path;
|
|
@@ -608,34 +793,49 @@ class QueryBuilder {
|
|
|
608
793
|
}
|
|
609
794
|
prepareFields(fields, type = 'where') {
|
|
610
795
|
const ret = [];
|
|
796
|
+
const getFieldName = (name) => {
|
|
797
|
+
if (type === 'groupBy') {
|
|
798
|
+
return this.helper.mapper(name, this.type, undefined, null);
|
|
799
|
+
}
|
|
800
|
+
return this.helper.mapper(name, this.type);
|
|
801
|
+
};
|
|
611
802
|
fields.forEach(field => {
|
|
803
|
+
const rawField = core_1.RawQueryFragment.getKnownFragment(field);
|
|
804
|
+
if (rawField) {
|
|
805
|
+
const sql = this.platform.formatQuery(rawField.sql, rawField.params);
|
|
806
|
+
ret.push(this.knex.raw(sql));
|
|
807
|
+
return;
|
|
808
|
+
}
|
|
612
809
|
if (!core_1.Utils.isString(field)) {
|
|
613
|
-
|
|
810
|
+
ret.push(field);
|
|
811
|
+
return;
|
|
614
812
|
}
|
|
615
813
|
const join = Object.keys(this._joins).find(k => field === k.substring(0, k.indexOf('#')));
|
|
616
814
|
if (join && type === 'where') {
|
|
617
|
-
|
|
815
|
+
ret.push(...this.helper.mapJoinColumns(this.type ?? enums_1.QueryType.SELECT, this._joins[join]));
|
|
816
|
+
return;
|
|
618
817
|
}
|
|
619
818
|
const [a, f] = this.helper.splitField(field);
|
|
620
819
|
const prop = this.helper.getProperty(f, a);
|
|
621
820
|
/* istanbul ignore next */
|
|
622
|
-
if (prop && [core_1.
|
|
821
|
+
if (prop && [core_1.ReferenceKind.ONE_TO_MANY, core_1.ReferenceKind.MANY_TO_MANY].includes(prop.kind)) {
|
|
623
822
|
return;
|
|
624
823
|
}
|
|
625
824
|
if (prop?.embedded) {
|
|
626
|
-
const
|
|
627
|
-
|
|
825
|
+
const name = prop.embeddedPath?.join('.') ?? prop.fieldNames[0];
|
|
826
|
+
const aliased = this._aliases[a] ? `${a}.${name}` : name;
|
|
827
|
+
ret.push(getFieldName(aliased));
|
|
628
828
|
return;
|
|
629
829
|
}
|
|
630
|
-
if (prop?.
|
|
830
|
+
if (prop?.kind === core_1.ReferenceKind.EMBEDDED) {
|
|
631
831
|
if (prop.object) {
|
|
632
|
-
ret.push(
|
|
832
|
+
ret.push(getFieldName(prop.fieldNames[0]));
|
|
633
833
|
}
|
|
634
834
|
else {
|
|
635
835
|
const nest = (prop) => {
|
|
636
836
|
for (const childProp of Object.values(prop.embeddedProps)) {
|
|
637
|
-
if (childProp.fieldNames) {
|
|
638
|
-
ret.push(
|
|
837
|
+
if (childProp.fieldNames && (childProp.kind !== core_1.ReferenceKind.EMBEDDED || childProp.object) && childProp.persist !== false) {
|
|
838
|
+
ret.push(getFieldName(childProp.fieldNames[0]));
|
|
639
839
|
}
|
|
640
840
|
else {
|
|
641
841
|
nest(childProp);
|
|
@@ -646,11 +846,15 @@ class QueryBuilder {
|
|
|
646
846
|
}
|
|
647
847
|
return;
|
|
648
848
|
}
|
|
649
|
-
|
|
849
|
+
if (prop && prop.fieldNames.length > 1) {
|
|
850
|
+
ret.push(...prop.fieldNames.map(f => getFieldName(f)));
|
|
851
|
+
return;
|
|
852
|
+
}
|
|
853
|
+
ret.push(getFieldName(field));
|
|
650
854
|
});
|
|
651
855
|
const meta = this.mainAlias.metadata;
|
|
652
856
|
/* istanbul ignore next */
|
|
653
|
-
const requiresSQLConversion = meta?.props.filter(p => p.
|
|
857
|
+
const requiresSQLConversion = meta?.props.filter(p => p.hasConvertToJSValueSQL && p.persist !== false) ?? [];
|
|
654
858
|
if (this.flags.has(core_1.QueryFlag.CONVERT_CUSTOM_TYPES) && (fields.includes('*') || fields.includes(`${this.mainAlias.aliasName}.*`)) && requiresSQLConversion.length > 0) {
|
|
655
859
|
requiresSQLConversion.forEach(p => ret.push(this.helper.mapper(p.name, this.type)));
|
|
656
860
|
}
|
|
@@ -659,7 +863,7 @@ class QueryBuilder {
|
|
|
659
863
|
const cols = this.helper.mapJoinColumns(this.type ?? enums_1.QueryType.SELECT, this._joins[f]);
|
|
660
864
|
ret.push(...cols);
|
|
661
865
|
}
|
|
662
|
-
if (this._joins[f].prop.
|
|
866
|
+
if (this._joins[f].prop.kind !== core_1.ReferenceKind.ONE_TO_ONE && this._joins[f].inverseJoinColumns) {
|
|
663
867
|
this._joins[f].inverseJoinColumns.forEach(inverseJoinColumn => {
|
|
664
868
|
core_1.Utils.renameKey(this._cond, inverseJoinColumn, `${this._joins[f].alias}.${inverseJoinColumn}`);
|
|
665
869
|
});
|
|
@@ -678,10 +882,9 @@ class QueryBuilder {
|
|
|
678
882
|
}
|
|
679
883
|
if (data) {
|
|
680
884
|
if (core_1.Utils.isEntity(data)) {
|
|
681
|
-
// data = helper(data).toJSON();
|
|
682
885
|
data = this.em?.getComparator().prepareEntity(data) ?? (0, core_1.serialize)(data);
|
|
683
886
|
}
|
|
684
|
-
this._data = this.helper.processData(data, this.flags.has(core_1.QueryFlag.CONVERT_CUSTOM_TYPES));
|
|
887
|
+
this._data = this.helper.processData(data, this.flags.has(core_1.QueryFlag.CONVERT_CUSTOM_TYPES), false);
|
|
685
888
|
}
|
|
686
889
|
if (cond) {
|
|
687
890
|
this.where(cond);
|
|
@@ -691,6 +894,8 @@ class QueryBuilder {
|
|
|
691
894
|
getQueryBase() {
|
|
692
895
|
const qb = this.getKnex();
|
|
693
896
|
const schema = this.getSchema(this.mainAlias);
|
|
897
|
+
// Joined tables doesn't need to belong to the same schema as the main table
|
|
898
|
+
const joinSchema = this._schema ?? this.em?.schema ?? schema;
|
|
694
899
|
if (schema) {
|
|
695
900
|
qb.withSchema(schema);
|
|
696
901
|
}
|
|
@@ -709,12 +914,12 @@ class QueryBuilder {
|
|
|
709
914
|
else if (this.flags.has(core_1.QueryFlag.DISTINCT)) {
|
|
710
915
|
qb.distinct();
|
|
711
916
|
}
|
|
712
|
-
this.helper.processJoins(qb, this._joins,
|
|
917
|
+
this.helper.processJoins(qb, this._joins, joinSchema);
|
|
713
918
|
break;
|
|
714
919
|
case enums_1.QueryType.COUNT: {
|
|
715
920
|
const m = this.flags.has(core_1.QueryFlag.DISTINCT) ? 'countDistinct' : 'count';
|
|
716
921
|
qb[m]({ count: this._fields.map(f => this.helper.mapper(f, this.type)) });
|
|
717
|
-
this.helper.processJoins(qb, this._joins,
|
|
922
|
+
this.helper.processJoins(qb, this._joins, joinSchema);
|
|
718
923
|
break;
|
|
719
924
|
}
|
|
720
925
|
case enums_1.QueryType.INSERT:
|
|
@@ -733,6 +938,24 @@ class QueryBuilder {
|
|
|
733
938
|
}
|
|
734
939
|
return qb;
|
|
735
940
|
}
|
|
941
|
+
applyDiscriminatorCondition() {
|
|
942
|
+
const meta = this.mainAlias.metadata;
|
|
943
|
+
if (!meta?.discriminatorValue) {
|
|
944
|
+
return;
|
|
945
|
+
}
|
|
946
|
+
const types = Object.values(meta.root.discriminatorMap).map(cls => this.metadata.find(cls));
|
|
947
|
+
const children = [];
|
|
948
|
+
const lookUpChildren = (ret, type) => {
|
|
949
|
+
const children = types.filter(meta2 => meta2.extends === type);
|
|
950
|
+
children.forEach(m => lookUpChildren(ret, m.className));
|
|
951
|
+
ret.push(...children.filter(c => c.discriminatorValue));
|
|
952
|
+
return children;
|
|
953
|
+
};
|
|
954
|
+
lookUpChildren(children, meta.className);
|
|
955
|
+
this.andWhere({
|
|
956
|
+
[meta.root.discriminatorColumn]: children.length > 0 ? { $in: [meta.discriminatorValue, ...children.map(c => c.discriminatorValue)] } : meta.discriminatorValue,
|
|
957
|
+
});
|
|
958
|
+
}
|
|
736
959
|
finalize() {
|
|
737
960
|
if (this.finalized) {
|
|
738
961
|
return;
|
|
@@ -741,10 +964,11 @@ class QueryBuilder {
|
|
|
741
964
|
this.select('*');
|
|
742
965
|
}
|
|
743
966
|
const meta = this.mainAlias.metadata;
|
|
967
|
+
this.applyDiscriminatorCondition();
|
|
744
968
|
if (meta && this.flags.has(core_1.QueryFlag.AUTO_JOIN_ONE_TO_ONE_OWNER)) {
|
|
745
969
|
const relationsToPopulate = this._populate.map(({ field }) => field);
|
|
746
970
|
meta.relations
|
|
747
|
-
.filter(prop => prop.
|
|
971
|
+
.filter(prop => prop.kind === core_1.ReferenceKind.ONE_TO_ONE && !prop.owner && !relationsToPopulate.includes(prop.name))
|
|
748
972
|
.map(prop => ({ field: prop.name }))
|
|
749
973
|
.forEach(item => this._populate.push(item));
|
|
750
974
|
}
|
|
@@ -753,16 +977,14 @@ class QueryBuilder {
|
|
|
753
977
|
const aliasedField = `${fromAlias}.${fromField}`;
|
|
754
978
|
const join = Object.keys(this._joins).find(k => `${aliasedField}#${this._joins[k].alias}` === k);
|
|
755
979
|
if (join && this._joins[join] && this.helper.isOneToOneInverse(fromField)) {
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
if (this.metadata.find(field)?.pivotTable) { // pivot table entity
|
|
759
|
-
this.autoJoinPivotTable(field);
|
|
980
|
+
this._populateMap[join] = this._joins[join].alias;
|
|
981
|
+
return;
|
|
760
982
|
}
|
|
761
|
-
|
|
983
|
+
if (meta && this.helper.isOneToOneInverse(fromField)) {
|
|
762
984
|
const prop = meta.properties[fromField];
|
|
763
985
|
const alias = this.getNextAlias(prop.pivotEntity ?? prop.type);
|
|
764
986
|
const aliasedName = `${fromAlias}.${prop.name}#${alias}`;
|
|
765
|
-
this._joins[aliasedName] = this.helper.joinOneToReference(prop, this.mainAlias.aliasName, alias,
|
|
987
|
+
this._joins[aliasedName] = this.helper.joinOneToReference(prop, this.mainAlias.aliasName, alias, enums_1.JoinType.leftJoin);
|
|
766
988
|
this._populateMap[aliasedName] = this._joins[aliasedName].alias;
|
|
767
989
|
}
|
|
768
990
|
});
|
|
@@ -774,14 +996,20 @@ class QueryBuilder {
|
|
|
774
996
|
const aliased = this.knex.ref(prop.fieldNames[0]).toString();
|
|
775
997
|
return `${prop.formula(alias)} as ${aliased}`;
|
|
776
998
|
})
|
|
777
|
-
.filter(field => !this._fields.
|
|
778
|
-
|
|
999
|
+
.filter(field => !this._fields.some(f => {
|
|
1000
|
+
if (f instanceof core_1.RawQueryFragment) {
|
|
1001
|
+
return f.sql === field && f.params.length === 0;
|
|
1002
|
+
}
|
|
1003
|
+
return f === field;
|
|
1004
|
+
}))
|
|
1005
|
+
.forEach(field => this._fields.push((0, core_1.raw)(field)));
|
|
779
1006
|
}
|
|
1007
|
+
this.processPopulateWhere();
|
|
780
1008
|
core_1.QueryHelper.processObjectParams(this._data);
|
|
781
1009
|
core_1.QueryHelper.processObjectParams(this._cond);
|
|
782
1010
|
core_1.QueryHelper.processObjectParams(this._having);
|
|
783
|
-
// automatically enable paginate flag when we detect to-many joins
|
|
784
|
-
if (!this.flags.has(core_1.QueryFlag.DISABLE_PAGINATE) && this.hasToManyJoins()) {
|
|
1011
|
+
// automatically enable paginate flag when we detect to-many joins, but only if there is no `group by` clause
|
|
1012
|
+
if (!this.flags.has(core_1.QueryFlag.DISABLE_PAGINATE) && this._groupBy.length === 0 && this.hasToManyJoins()) {
|
|
785
1013
|
this.flags.add(core_1.QueryFlag.PAGINATE);
|
|
786
1014
|
}
|
|
787
1015
|
if (meta && this.flags.has(core_1.QueryFlag.PAGINATE) && (this._limit > 0 || this._offset > 0)) {
|
|
@@ -792,17 +1020,54 @@ class QueryBuilder {
|
|
|
792
1020
|
}
|
|
793
1021
|
this.finalized = true;
|
|
794
1022
|
}
|
|
1023
|
+
processPopulateWhere() {
|
|
1024
|
+
if (this._populateWhere == null || this._populateWhere === core_1.PopulateHint.ALL) {
|
|
1025
|
+
return;
|
|
1026
|
+
}
|
|
1027
|
+
const joins = Object.values(this._joins);
|
|
1028
|
+
joins.forEach(join => {
|
|
1029
|
+
join.cond_ = join.cond;
|
|
1030
|
+
join.cond = {};
|
|
1031
|
+
});
|
|
1032
|
+
const replaceOnConditions = (cond) => {
|
|
1033
|
+
Object.keys(cond).forEach(k => {
|
|
1034
|
+
if (core_1.Utils.isOperator(k)) {
|
|
1035
|
+
if (Array.isArray(cond[k])) {
|
|
1036
|
+
return cond[k].forEach((c) => replaceOnConditions(c));
|
|
1037
|
+
}
|
|
1038
|
+
return replaceOnConditions(cond[k]);
|
|
1039
|
+
}
|
|
1040
|
+
const [a] = this.helper.splitField(k);
|
|
1041
|
+
const join = joins.find(j => j.alias === a);
|
|
1042
|
+
if (join) {
|
|
1043
|
+
join.cond = { ...join.cond, [k]: cond[k] };
|
|
1044
|
+
}
|
|
1045
|
+
});
|
|
1046
|
+
};
|
|
1047
|
+
if (this._populateWhere === core_1.PopulateHint.INFER) {
|
|
1048
|
+
replaceOnConditions(this._cond);
|
|
1049
|
+
}
|
|
1050
|
+
else if (typeof this._populateWhere === 'object') {
|
|
1051
|
+
const cond = CriteriaNodeFactory_1.CriteriaNodeFactory
|
|
1052
|
+
.createNode(this.metadata, this.mainAlias.entityName, this._populateWhere)
|
|
1053
|
+
.process(this);
|
|
1054
|
+
replaceOnConditions(cond);
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
795
1057
|
hasToManyJoins() {
|
|
796
1058
|
return Object.values(this._joins).some(join => {
|
|
797
|
-
return [core_1.
|
|
1059
|
+
return [core_1.ReferenceKind.ONE_TO_MANY, core_1.ReferenceKind.MANY_TO_MANY].includes(join.prop.kind);
|
|
798
1060
|
});
|
|
799
1061
|
}
|
|
800
1062
|
wrapPaginateSubQuery(meta) {
|
|
801
1063
|
const pks = this.prepareFields(meta.primaryKeys, 'sub-query');
|
|
802
1064
|
const subQuery = this.clone().select(pks).groupBy(pks).limit(this._limit);
|
|
1065
|
+
// revert the on conditions added via populateWhere, we want to apply those only once
|
|
1066
|
+
Object.values(subQuery._joins).forEach(join => join.cond = join.cond_ ?? {});
|
|
803
1067
|
if (this._offset) {
|
|
804
1068
|
subQuery.offset(this._offset);
|
|
805
1069
|
}
|
|
1070
|
+
const addToSelect = [];
|
|
806
1071
|
if (this._orderBy.length > 0) {
|
|
807
1072
|
const orderBy = [];
|
|
808
1073
|
for (const orderMap of this._orderBy) {
|
|
@@ -810,28 +1075,38 @@ class QueryBuilder {
|
|
|
810
1075
|
const [a, f] = this.helper.splitField(field);
|
|
811
1076
|
const prop = this.helper.getProperty(f, a);
|
|
812
1077
|
const type = this.platform.castColumn(prop);
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
1078
|
+
const fieldName = this.helper.mapper(field, this.type, undefined, null);
|
|
1079
|
+
if (!prop?.persist && !prop?.formula) {
|
|
1080
|
+
addToSelect.push(fieldName);
|
|
1081
|
+
}
|
|
1082
|
+
orderBy.push({ [(0, core_1.raw)(`min(${this.knex.ref(fieldName)}${type})`)]: direction });
|
|
816
1083
|
}
|
|
817
1084
|
}
|
|
818
1085
|
subQuery.orderBy(orderBy);
|
|
819
1086
|
}
|
|
820
1087
|
subQuery.finalized = true;
|
|
821
1088
|
const knexQuery = subQuery.as(this.mainAlias.aliasName).clearSelect().select(pks);
|
|
1089
|
+
if (addToSelect.length > 0) {
|
|
1090
|
+
addToSelect.forEach(prop => {
|
|
1091
|
+
const field = this._fields.find(field => {
|
|
1092
|
+
if (typeof field === 'object' && field && '__as' in field) {
|
|
1093
|
+
return field.__as === prop;
|
|
1094
|
+
}
|
|
1095
|
+
// not perfect, but should work most of the time, ideally we should check only the alias (`... as alias`)
|
|
1096
|
+
return field.toString().includes(prop);
|
|
1097
|
+
});
|
|
1098
|
+
if (field) {
|
|
1099
|
+
knexQuery.select(field);
|
|
1100
|
+
}
|
|
1101
|
+
});
|
|
1102
|
+
}
|
|
822
1103
|
// multiple sub-queries are needed to get around mysql limitations with order by + limit + where in + group by (o.O)
|
|
823
1104
|
// https://stackoverflow.com/questions/17892762/mysql-this-version-of-mysql-doesnt-yet-support-limit-in-all-any-some-subqu
|
|
824
1105
|
const subSubQuery = this.getKnex().select(pks).from(knexQuery);
|
|
1106
|
+
subSubQuery.__raw = true; // tag it as there is now way to check via `instanceof`
|
|
825
1107
|
this._limit = undefined;
|
|
826
1108
|
this._offset = undefined;
|
|
827
|
-
const cond = this._cond;
|
|
828
1109
|
this.select(this._fields).where({ [core_1.Utils.getPrimaryKeyHash(meta.primaryKeys)]: { $in: subSubQuery } });
|
|
829
|
-
if (this._populateWhere === core_1.PopulateHint.INFER) {
|
|
830
|
-
this.andWhere(cond);
|
|
831
|
-
}
|
|
832
|
-
else if (typeof this._populateWhere === 'object') {
|
|
833
|
-
this.andWhere(this._populateWhere);
|
|
834
|
-
}
|
|
835
1110
|
}
|
|
836
1111
|
wrapModifySubQuery(meta) {
|
|
837
1112
|
const subQuery = this.clone();
|
|
@@ -845,21 +1120,10 @@ class QueryBuilder {
|
|
|
845
1120
|
[core_1.Utils.getPrimaryKeyHash(meta.primaryKeys)]: { $in: subSubQuery },
|
|
846
1121
|
});
|
|
847
1122
|
}
|
|
848
|
-
autoJoinPivotTable(field) {
|
|
849
|
-
const pivotMeta = this.metadata.find(field);
|
|
850
|
-
const owner = pivotMeta.relations[0];
|
|
851
|
-
const inverse = pivotMeta.relations[1];
|
|
852
|
-
const prop = this._cond[pivotMeta.name + '.' + owner.name] || this._orderBy[pivotMeta.name + '.' + owner.name] ? inverse : owner;
|
|
853
|
-
const pivotAlias = this.getNextAlias(pivotMeta.name);
|
|
854
|
-
this._joins[field] = this.helper.joinPivotTable(field, prop, this.mainAlias.aliasName, pivotAlias, 'leftJoin');
|
|
855
|
-
core_1.Utils.renameKey(this._cond, `${field}.${owner.name}`, core_1.Utils.getPrimaryKeyHash(owner.fieldNames.map(fieldName => `${pivotAlias}.${fieldName}`)));
|
|
856
|
-
core_1.Utils.renameKey(this._cond, `${field}.${inverse.name}`, core_1.Utils.getPrimaryKeyHash(inverse.fieldNames.map(fieldName => `${pivotAlias}.${fieldName}`)));
|
|
857
|
-
this._populateMap[field] = this._joins[field].alias;
|
|
858
|
-
}
|
|
859
1123
|
getSchema(alias) {
|
|
860
1124
|
const { metadata } = alias;
|
|
861
1125
|
const metaSchema = metadata?.schema && metadata.schema !== '*' ? metadata.schema : undefined;
|
|
862
|
-
return this._schema ?? metaSchema ?? this.em?.config.get('schema');
|
|
1126
|
+
return this._schema ?? metaSchema ?? this.em?.schema ?? this.em?.config.get('schema');
|
|
863
1127
|
}
|
|
864
1128
|
createAlias(entityName, aliasName, subQuery) {
|
|
865
1129
|
const metadata = this.metadata.find(entityName);
|
|
@@ -875,11 +1139,11 @@ class QueryBuilder {
|
|
|
875
1139
|
fromSubQuery(target, aliasName) {
|
|
876
1140
|
const subQuery = target.getKnexQuery();
|
|
877
1141
|
const { entityName } = target.mainAlias;
|
|
878
|
-
aliasName
|
|
1142
|
+
aliasName ??= this.getNextAlias(entityName);
|
|
879
1143
|
this.createMainAlias(entityName, aliasName, subQuery);
|
|
880
1144
|
}
|
|
881
1145
|
fromEntityName(entityName, aliasName) {
|
|
882
|
-
aliasName
|
|
1146
|
+
aliasName ??= this._mainAlias?.aliasName ?? this.getNextAlias(entityName);
|
|
883
1147
|
this.createMainAlias(entityName, aliasName);
|
|
884
1148
|
}
|
|
885
1149
|
createQueryBuilderHelper() {
|
|
@@ -897,6 +1161,7 @@ class QueryBuilder {
|
|
|
897
1161
|
}
|
|
898
1162
|
}
|
|
899
1163
|
/* istanbul ignore next */
|
|
1164
|
+
/** @ignore */
|
|
900
1165
|
[util_1.inspect.custom](depth) {
|
|
901
1166
|
const object = { ...this };
|
|
902
1167
|
const hidden = ['metadata', 'driver', 'context', 'platform', 'knex', 'type'];
|
|
@@ -907,6 +1172,9 @@ class QueryBuilder {
|
|
|
907
1172
|
if (this._data) {
|
|
908
1173
|
object.data = this._data;
|
|
909
1174
|
}
|
|
1175
|
+
if (this._schema) {
|
|
1176
|
+
object.schema = this._schema;
|
|
1177
|
+
}
|
|
910
1178
|
if (!core_1.Utils.isEmpty(this._cond)) {
|
|
911
1179
|
object.where = this._cond;
|
|
912
1180
|
}
|