@mikro-orm/knex 6.0.0-dev.14 → 6.0.0-dev.140
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 +394 -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 +5 -9
- package/SqlEntityRepository.d.ts +7 -6
- package/SqlEntityRepository.js +11 -8
- package/index.mjs +24 -6
- package/package.json +8 -8
- package/query/ArrayCriteriaNode.d.ts +3 -3
- package/query/CriteriaNode.d.ts +9 -8
- package/query/CriteriaNode.js +17 -9
- package/query/CriteriaNodeFactory.d.ts +6 -6
- package/query/CriteriaNodeFactory.js +17 -13
- package/query/ObjectCriteriaNode.d.ts +3 -3
- package/query/ObjectCriteriaNode.js +14 -10
- package/query/QueryBuilder.d.ts +50 -21
- package/query/QueryBuilder.js +389 -132
- 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 +27 -9
- package/schema/DatabaseTable.d.ts +4 -2
- package/schema/DatabaseTable.js +67 -32
- package/schema/SchemaComparator.d.ts +3 -3
- package/schema/SchemaComparator.js +50 -30
- package/schema/SchemaHelper.d.ts +6 -4
- package/schema/SchemaHelper.js +18 -7
- package/schema/SqlSchemaGenerator.d.ts +3 -6
- package/schema/SqlSchemaGenerator.js +69 -34
- package/typings.d.ts +17 -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,46 @@ 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
|
+
}
|
|
189
|
+
this.addSelect(this.getFieldsForJoinedLoad(prop, alias, fields));
|
|
140
190
|
const populate = this._joinedProps.get(fromAlias);
|
|
141
191
|
const item = { field: prop.name, strategy: core_1.LoadStrategy.JOINED, children: [] };
|
|
142
192
|
if (populate) {
|
|
@@ -148,13 +198,22 @@ class QueryBuilder {
|
|
|
148
198
|
this._joinedProps.set(alias, item);
|
|
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 = [];
|
|
156
215
|
prop.targetMeta.props
|
|
157
|
-
.filter(prop => this.platform.shouldHaveColumn(prop, this._populate))
|
|
216
|
+
.filter(prop => explicitFields ? explicitFields.includes(prop.name) || prop.primary : this.platform.shouldHaveColumn(prop, this._populate))
|
|
158
217
|
.forEach(prop => fields.push(...this.driver.mapPropToFieldNames(this, prop, alias)));
|
|
159
218
|
return fields;
|
|
160
219
|
}
|
|
@@ -165,9 +224,15 @@ class QueryBuilder {
|
|
|
165
224
|
}
|
|
166
225
|
where(cond, params, operator) {
|
|
167
226
|
this.ensureNotFinalized();
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
227
|
+
const rawField = core_1.RawQueryFragment.getKnownFragment(cond);
|
|
228
|
+
if (rawField) {
|
|
229
|
+
const sql = this.platform.formatQuery(rawField.sql, rawField.params);
|
|
230
|
+
cond = { [(0, core_1.raw)(`(${sql})`)]: core_1.Utils.asArray(params) };
|
|
231
|
+
operator ??= '$and';
|
|
232
|
+
}
|
|
233
|
+
else if (core_1.Utils.isString(cond)) {
|
|
234
|
+
cond = { [(0, core_1.raw)(`(${cond})`, core_1.Utils.asArray(params))]: [] };
|
|
235
|
+
operator ??= '$and';
|
|
171
236
|
}
|
|
172
237
|
else {
|
|
173
238
|
cond = core_1.QueryHelper.processWhere({
|
|
@@ -222,6 +287,7 @@ class QueryBuilder {
|
|
|
222
287
|
aliasMap: this.getAliasMap(),
|
|
223
288
|
aliased: !this.type || [enums_1.QueryType.SELECT, enums_1.QueryType.COUNT].includes(this.type),
|
|
224
289
|
convertCustomTypes: false,
|
|
290
|
+
type: 'orderBy',
|
|
225
291
|
});
|
|
226
292
|
this._orderBy.push(CriteriaNodeFactory_1.CriteriaNodeFactory.createNode(this.metadata, this.mainAlias.entityName, processed).process(this));
|
|
227
293
|
});
|
|
@@ -235,15 +301,22 @@ class QueryBuilder {
|
|
|
235
301
|
having(cond = {}, params) {
|
|
236
302
|
this.ensureNotFinalized();
|
|
237
303
|
if (core_1.Utils.isString(cond)) {
|
|
238
|
-
cond = { [`(${cond})
|
|
304
|
+
cond = { [(0, core_1.raw)(`(${cond})`, params)]: [] };
|
|
239
305
|
}
|
|
240
306
|
this._having = CriteriaNodeFactory_1.CriteriaNodeFactory.createNode(this.metadata, this.mainAlias.entityName, cond).process(this);
|
|
241
307
|
return this;
|
|
242
308
|
}
|
|
243
309
|
onConflict(fields = []) {
|
|
310
|
+
const meta = this.mainAlias.metadata;
|
|
244
311
|
this.ensureNotFinalized();
|
|
245
|
-
this._onConflict
|
|
246
|
-
this._onConflict.push({
|
|
312
|
+
this._onConflict ??= [];
|
|
313
|
+
this._onConflict.push({
|
|
314
|
+
fields: core_1.Utils.asArray(fields).flatMap(f => {
|
|
315
|
+
const key = f.toString();
|
|
316
|
+
/* istanbul ignore next */
|
|
317
|
+
return meta.properties[key]?.fieldNames ?? [key];
|
|
318
|
+
}),
|
|
319
|
+
});
|
|
247
320
|
return this;
|
|
248
321
|
}
|
|
249
322
|
ignore() {
|
|
@@ -263,6 +336,10 @@ class QueryBuilder {
|
|
|
263
336
|
this._onConflict[this._onConflict.length - 1].merge = data;
|
|
264
337
|
return this;
|
|
265
338
|
}
|
|
339
|
+
returning(fields) {
|
|
340
|
+
this._returning = fields != null ? core_1.Utils.asArray(fields) : fields;
|
|
341
|
+
return this;
|
|
342
|
+
}
|
|
266
343
|
/**
|
|
267
344
|
* @internal
|
|
268
345
|
*/
|
|
@@ -272,17 +349,6 @@ class QueryBuilder {
|
|
|
272
349
|
this._populateWhere = populateWhere;
|
|
273
350
|
return this;
|
|
274
351
|
}
|
|
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
352
|
limit(limit, offset = 0) {
|
|
287
353
|
this.ensureNotFinalized();
|
|
288
354
|
this._limit = limit;
|
|
@@ -338,6 +404,24 @@ class QueryBuilder {
|
|
|
338
404
|
this._indexHint = sql;
|
|
339
405
|
return this;
|
|
340
406
|
}
|
|
407
|
+
/**
|
|
408
|
+
* Prepend comment to the sql query using the syntax `/* ... *‍/`. Some characters are forbidden such as `/*, *‍/` and `?`.
|
|
409
|
+
*/
|
|
410
|
+
comment(comment) {
|
|
411
|
+
this.ensureNotFinalized();
|
|
412
|
+
this._comments.push(...core_1.Utils.asArray(comment));
|
|
413
|
+
return this;
|
|
414
|
+
}
|
|
415
|
+
/**
|
|
416
|
+
* Add hints to the query using comment-like syntax `/*+ ... *‍/`. MySQL and Oracle use this syntax for optimizer hints.
|
|
417
|
+
* Also various DB proxies and routers use this syntax to pass hints to alter their behavior. In other dialects the hints
|
|
418
|
+
* are ignored as simple comments.
|
|
419
|
+
*/
|
|
420
|
+
hintComment(comment) {
|
|
421
|
+
this.ensureNotFinalized();
|
|
422
|
+
this._hintComments.push(...core_1.Utils.asArray(comment));
|
|
423
|
+
return this;
|
|
424
|
+
}
|
|
341
425
|
from(target, aliasName) {
|
|
342
426
|
this.ensureNotFinalized();
|
|
343
427
|
if (target instanceof QueryBuilder) {
|
|
@@ -355,17 +439,21 @@ class QueryBuilder {
|
|
|
355
439
|
getKnexQuery() {
|
|
356
440
|
this.finalize();
|
|
357
441
|
const qb = this.getQueryBase();
|
|
442
|
+
qb.__raw = true; // tag it as there is now way to check via `instanceof`
|
|
358
443
|
core_1.Utils.runIfNotEmpty(() => this.helper.appendQueryCondition(this.type ?? enums_1.QueryType.SELECT, this._cond, qb), this._cond && !this._onConflict);
|
|
359
444
|
core_1.Utils.runIfNotEmpty(() => qb.groupBy(this.prepareFields(this._groupBy, 'groupBy')), this._groupBy);
|
|
360
445
|
core_1.Utils.runIfNotEmpty(() => this.helper.appendQueryCondition(this.type ?? enums_1.QueryType.SELECT, this._having, qb, undefined, 'having'), this._having);
|
|
361
446
|
core_1.Utils.runIfNotEmpty(() => {
|
|
362
447
|
const queryOrder = this.helper.getQueryOrder(this.type ?? enums_1.QueryType.SELECT, this._orderBy, this._populateMap);
|
|
363
448
|
if (queryOrder) {
|
|
364
|
-
|
|
449
|
+
qb.orderByRaw(queryOrder);
|
|
450
|
+
return;
|
|
365
451
|
}
|
|
366
452
|
}, this._orderBy);
|
|
367
453
|
core_1.Utils.runIfNotEmpty(() => qb.limit(this._limit), this._limit != null);
|
|
368
454
|
core_1.Utils.runIfNotEmpty(() => qb.offset(this._offset), this._offset);
|
|
455
|
+
core_1.Utils.runIfNotEmpty(() => this._comments.forEach(comment => qb.comment(comment)), this._comments);
|
|
456
|
+
core_1.Utils.runIfNotEmpty(() => this._hintComments.forEach(comment => qb.hintComment(comment)), this._hintComments);
|
|
369
457
|
core_1.Utils.runIfNotEmpty(() => this.helper.appendOnConflictClause(this.type ?? enums_1.QueryType.SELECT, this._onConflict, qb), this._onConflict);
|
|
370
458
|
if (this.type === enums_1.QueryType.TRUNCATE && this.platform.usesCascadeStatement()) {
|
|
371
459
|
return this.knex.raw(qb.toSQL().toNative().sql + ' cascade');
|
|
@@ -373,26 +461,35 @@ class QueryBuilder {
|
|
|
373
461
|
if (this.lockMode) {
|
|
374
462
|
this.helper.getLockSQL(qb, this.lockMode, this.lockTables);
|
|
375
463
|
}
|
|
376
|
-
this.helper.finalize(this.type ?? enums_1.QueryType.SELECT, qb, this.mainAlias.metadata);
|
|
464
|
+
this.helper.finalize(this.type ?? enums_1.QueryType.SELECT, qb, this.mainAlias.metadata, this._data, this._returning);
|
|
377
465
|
return qb;
|
|
378
466
|
}
|
|
379
467
|
/**
|
|
380
468
|
* Returns the query with parameters as wildcards.
|
|
381
469
|
*/
|
|
382
470
|
getQuery() {
|
|
383
|
-
return this.
|
|
471
|
+
return this.toQuery().sql;
|
|
472
|
+
}
|
|
473
|
+
#query;
|
|
474
|
+
toQuery() {
|
|
475
|
+
if (this.#query) {
|
|
476
|
+
return this.#query;
|
|
477
|
+
}
|
|
478
|
+
const sql = this.getKnexQuery().toSQL();
|
|
479
|
+
const query = sql.toNative();
|
|
480
|
+
return this.#query = { sql: query.sql, params: query.bindings ?? [], _sql: sql };
|
|
384
481
|
}
|
|
385
482
|
/**
|
|
386
483
|
* Returns the list of all parameters for this query.
|
|
387
484
|
*/
|
|
388
485
|
getParams() {
|
|
389
|
-
return this.
|
|
486
|
+
return this.toQuery().params;
|
|
390
487
|
}
|
|
391
488
|
/**
|
|
392
489
|
* Returns raw interpolated query string with all the parameters inlined.
|
|
393
490
|
*/
|
|
394
491
|
getFormattedQuery() {
|
|
395
|
-
const query = this.
|
|
492
|
+
const query = this.toQuery()._sql;
|
|
396
493
|
return this.platform.formatQuery(query.sql, query.bindings);
|
|
397
494
|
}
|
|
398
495
|
/**
|
|
@@ -424,7 +521,10 @@ class QueryBuilder {
|
|
|
424
521
|
* Executes this QB and returns the raw results, mapped to the property names (unless disabled via last parameter).
|
|
425
522
|
* Use `method` to specify what kind of result you want to get (array/single/meta).
|
|
426
523
|
*/
|
|
427
|
-
async execute(method = 'all',
|
|
524
|
+
async execute(method = 'all', options) {
|
|
525
|
+
options = typeof options === 'boolean' ? { mapResults: options } : (options ?? {});
|
|
526
|
+
options.mergeResults ??= true;
|
|
527
|
+
options.mapResults ??= true;
|
|
428
528
|
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
529
|
this.connectionType = 'write';
|
|
430
530
|
}
|
|
@@ -433,21 +533,33 @@ class QueryBuilder {
|
|
|
433
533
|
if (cached?.data) {
|
|
434
534
|
return cached.data;
|
|
435
535
|
}
|
|
436
|
-
const
|
|
437
|
-
const
|
|
536
|
+
const write = method === 'run' || !this.platform.getConfig().get('preferReadReplicas');
|
|
537
|
+
const type = this.connectionType || (write ? 'write' : 'read');
|
|
538
|
+
const res = await this.driver.getConnection(type).execute(query.sql, query.bindings, method, this.context, this.logging);
|
|
438
539
|
const meta = this.mainAlias.metadata;
|
|
439
|
-
if (!mapResults || !meta) {
|
|
540
|
+
if (!options.mapResults || !meta) {
|
|
440
541
|
await this.em?.storeCache(this._cache, cached, res);
|
|
441
542
|
return res;
|
|
442
543
|
}
|
|
443
|
-
if (method === '
|
|
544
|
+
if (method === 'run') {
|
|
545
|
+
return res;
|
|
546
|
+
}
|
|
547
|
+
const joinedProps = this.driver.joinedProps(meta, this._populate);
|
|
548
|
+
let mapped;
|
|
549
|
+
if (Array.isArray(res)) {
|
|
444
550
|
const map = {};
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
551
|
+
mapped = res.map(r => this.driver.mapResult(r, meta, this._populate, this, map));
|
|
552
|
+
if (options.mergeResults && joinedProps.length > 0) {
|
|
553
|
+
mapped = this.driver.mergeJoinedResult(mapped, this.mainAlias.metadata, joinedProps);
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
else {
|
|
557
|
+
mapped = [this.driver.mapResult(res, meta, joinedProps, this)];
|
|
448
558
|
}
|
|
449
|
-
const mapped = this.driver.mapResult(res, meta, this._populate, this);
|
|
450
559
|
await this.em?.storeCache(this._cache, cached, mapped);
|
|
560
|
+
if (method === 'get') {
|
|
561
|
+
return mapped[0];
|
|
562
|
+
}
|
|
451
563
|
return mapped;
|
|
452
564
|
}
|
|
453
565
|
/**
|
|
@@ -461,11 +573,28 @@ class QueryBuilder {
|
|
|
461
573
|
*/
|
|
462
574
|
async getResultList() {
|
|
463
575
|
await this.em.tryFlush(this.mainAlias.entityName, { flushMode: this.flushMode });
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
576
|
+
const res = await this.execute('all', true);
|
|
577
|
+
const entities = [];
|
|
578
|
+
function propagatePopulateHint(entity, hint) {
|
|
579
|
+
(0, core_1.helper)(entity).__serializationContext.populate ??= hint;
|
|
580
|
+
hint.forEach(pop => {
|
|
581
|
+
const value = entity[pop.field];
|
|
582
|
+
if (core_1.Utils.isEntity(value, true)) {
|
|
583
|
+
(0, core_1.helper)(value).populated();
|
|
584
|
+
propagatePopulateHint(value, pop.children ?? []);
|
|
585
|
+
}
|
|
586
|
+
else if (core_1.Utils.isCollection(value)) {
|
|
587
|
+
value.populated();
|
|
588
|
+
value.getItems(false).forEach(item => propagatePopulateHint(item, pop.children ?? []));
|
|
589
|
+
}
|
|
590
|
+
});
|
|
467
591
|
}
|
|
468
|
-
|
|
592
|
+
for (const r of res) {
|
|
593
|
+
const entity = this.em.map(this.mainAlias.entityName, r, { schema: this._schema });
|
|
594
|
+
propagatePopulateHint(entity, this._populate);
|
|
595
|
+
entities.push(entity);
|
|
596
|
+
}
|
|
597
|
+
return entities;
|
|
469
598
|
}
|
|
470
599
|
/**
|
|
471
600
|
* Executes the query, returning the first result or null
|
|
@@ -483,17 +612,30 @@ class QueryBuilder {
|
|
|
483
612
|
res = await this.execute('get', false);
|
|
484
613
|
}
|
|
485
614
|
else {
|
|
486
|
-
const qb = this.clone();
|
|
615
|
+
const qb = this.type === undefined ? this : this.clone();
|
|
487
616
|
qb.count(field, distinct ?? qb.hasToManyJoins()).limit(undefined).offset(undefined).orderBy([]);
|
|
488
617
|
res = await qb.execute('get', false);
|
|
489
618
|
}
|
|
490
619
|
return res ? +res.count : 0;
|
|
491
620
|
}
|
|
621
|
+
/**
|
|
622
|
+
* Executes the query, returning both array of results and total count query (without offset and limit).
|
|
623
|
+
*/
|
|
624
|
+
async getResultAndCount() {
|
|
625
|
+
return Promise.all([
|
|
626
|
+
this.getResultList(),
|
|
627
|
+
this.getCount(),
|
|
628
|
+
]);
|
|
629
|
+
}
|
|
492
630
|
/**
|
|
493
631
|
* Provides promise-like interface so we can await the QB instance.
|
|
494
632
|
*/
|
|
495
633
|
then(onfulfilled, onrejected) {
|
|
496
|
-
|
|
634
|
+
let type = this.type ?? enums_1.QueryType.SELECT;
|
|
635
|
+
if (this.flags.has(core_1.QueryFlag.UPDATE_SUB_QUERY) || this.flags.has(core_1.QueryFlag.DELETE_SUB_QUERY)) {
|
|
636
|
+
type = enums_1.QueryType.UPDATE;
|
|
637
|
+
}
|
|
638
|
+
switch (type) {
|
|
497
639
|
case enums_1.QueryType.INSERT:
|
|
498
640
|
case enums_1.QueryType.UPDATE:
|
|
499
641
|
case enums_1.QueryType.DELETE:
|
|
@@ -516,7 +658,10 @@ class QueryBuilder {
|
|
|
516
658
|
/* istanbul ignore next */
|
|
517
659
|
alias = meta?.properties[f]?.fieldNames[0] ?? alias;
|
|
518
660
|
}
|
|
519
|
-
|
|
661
|
+
const ret = qb.as(alias);
|
|
662
|
+
// tag the instance, so it is possible to detect it easily
|
|
663
|
+
Object.defineProperty(ret, '__as', { enumerable: false, value: alias });
|
|
664
|
+
return ret;
|
|
520
665
|
}
|
|
521
666
|
clone() {
|
|
522
667
|
const qb = new QueryBuilder(this.mainAlias.entityName, this.metadata, this.driver, this.context, this.mainAlias.aliasName, this.connectionType, this.em);
|
|
@@ -524,7 +669,8 @@ class QueryBuilder {
|
|
|
524
669
|
// clone array/object properties
|
|
525
670
|
const properties = [
|
|
526
671
|
'flags', '_populate', '_populateWhere', '_populateMap', '_joins', '_joinedProps', '_cond', '_data', '_orderBy',
|
|
527
|
-
'_schema', '_indexHint', '_cache', 'subQueries', 'lockMode', 'lockTables',
|
|
672
|
+
'_schema', '_indexHint', '_cache', 'subQueries', 'lockMode', 'lockTables', '_groupBy', '_having', '_returning',
|
|
673
|
+
'_comments', '_hintComments',
|
|
528
674
|
];
|
|
529
675
|
properties.forEach(prop => qb[prop] = core_1.Utils.copy(this[prop]));
|
|
530
676
|
/* istanbul ignore else */
|
|
@@ -552,14 +698,42 @@ class QueryBuilder {
|
|
|
552
698
|
}
|
|
553
699
|
return qb;
|
|
554
700
|
}
|
|
555
|
-
joinReference(field, alias, cond, type, path) {
|
|
701
|
+
joinReference(field, alias, cond, type, path, schema, subquery) {
|
|
556
702
|
this.ensureNotFinalized();
|
|
703
|
+
if (typeof field === 'object') {
|
|
704
|
+
const prop = {
|
|
705
|
+
name: '__subquery__',
|
|
706
|
+
kind: core_1.ReferenceKind.MANY_TO_ONE,
|
|
707
|
+
};
|
|
708
|
+
if (field instanceof QueryBuilder) {
|
|
709
|
+
prop.type = field.mainAlias.entityName;
|
|
710
|
+
prop.targetMeta = field.mainAlias.metadata;
|
|
711
|
+
field = field.getKnexQuery();
|
|
712
|
+
}
|
|
713
|
+
this._joins[`${this.alias}.${prop.name}#${alias}`] = {
|
|
714
|
+
prop,
|
|
715
|
+
alias,
|
|
716
|
+
type,
|
|
717
|
+
cond,
|
|
718
|
+
schema,
|
|
719
|
+
subquery: field.toString(),
|
|
720
|
+
ownerAlias: this.alias,
|
|
721
|
+
};
|
|
722
|
+
return prop;
|
|
723
|
+
}
|
|
724
|
+
if (!subquery && type.includes('lateral')) {
|
|
725
|
+
throw new Error(`Lateral join can be used only with a sub-query.`);
|
|
726
|
+
}
|
|
557
727
|
const [fromAlias, fromField] = this.helper.splitField(field);
|
|
558
|
-
const
|
|
728
|
+
const q = (str) => `'${str}'`;
|
|
729
|
+
if (!this._aliases[fromAlias]) {
|
|
730
|
+
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(', ')}.`);
|
|
731
|
+
}
|
|
732
|
+
const entityName = this._aliases[fromAlias].entityName;
|
|
559
733
|
const meta = this.metadata.get(entityName);
|
|
560
734
|
const prop = meta.properties[fromField];
|
|
561
735
|
if (!prop) {
|
|
562
|
-
throw new Error(`Trying to join ${field}, but ${fromField} is not a defined relation on ${meta.className}
|
|
736
|
+
throw new Error(`Trying to join ${q(field)}, but ${q(fromField)} is not a defined relation on ${meta.className}.`);
|
|
563
737
|
}
|
|
564
738
|
this.createAlias(prop.type, alias);
|
|
565
739
|
cond = core_1.QueryHelper.processWhere({
|
|
@@ -571,26 +745,26 @@ class QueryBuilder {
|
|
|
571
745
|
aliased: !this.type || [enums_1.QueryType.SELECT, enums_1.QueryType.COUNT].includes(this.type),
|
|
572
746
|
});
|
|
573
747
|
let aliasedName = `${fromAlias}.${prop.name}#${alias}`;
|
|
574
|
-
path
|
|
575
|
-
if (prop.
|
|
576
|
-
this._joins[aliasedName] = this.helper.joinOneToReference(prop, fromAlias, alias, type, cond);
|
|
748
|
+
path ??= `${(Object.values(this._joins).find(j => j.alias === fromAlias)?.path ?? entityName)}.${prop.name}`;
|
|
749
|
+
if (prop.kind === core_1.ReferenceKind.ONE_TO_MANY) {
|
|
750
|
+
this._joins[aliasedName] = this.helper.joinOneToReference(prop, fromAlias, alias, type, cond, schema);
|
|
577
751
|
}
|
|
578
|
-
else if (prop.
|
|
752
|
+
else if (prop.kind === core_1.ReferenceKind.MANY_TO_MANY) {
|
|
579
753
|
let pivotAlias = alias;
|
|
580
|
-
if (type !==
|
|
754
|
+
if (type !== enums_1.JoinType.pivotJoin) {
|
|
581
755
|
const oldPivotAlias = this.getAliasForJoinPath(path + '[pivot]');
|
|
582
756
|
pivotAlias = oldPivotAlias ?? this.getNextAlias(prop.pivotEntity);
|
|
583
757
|
aliasedName = `${fromAlias}.${prop.name}#${pivotAlias}`;
|
|
584
758
|
}
|
|
585
|
-
const joins = this.helper.joinManyToManyReference(prop, fromAlias, alias, pivotAlias, type, cond, path);
|
|
759
|
+
const joins = this.helper.joinManyToManyReference(prop, fromAlias, alias, pivotAlias, type, cond, path, schema);
|
|
586
760
|
Object.assign(this._joins, joins);
|
|
587
761
|
this.createAlias(prop.pivotEntity, pivotAlias);
|
|
588
762
|
}
|
|
589
|
-
else if (prop.
|
|
590
|
-
this._joins[aliasedName] = this.helper.joinOneToReference(prop, fromAlias, alias, type, cond);
|
|
763
|
+
else if (prop.kind === core_1.ReferenceKind.ONE_TO_ONE) {
|
|
764
|
+
this._joins[aliasedName] = this.helper.joinOneToReference(prop, fromAlias, alias, type, cond, schema);
|
|
591
765
|
}
|
|
592
766
|
else { // MANY_TO_ONE
|
|
593
|
-
this._joins[aliasedName] = this.helper.joinManyToOneReference(prop, fromAlias, alias, type, cond);
|
|
767
|
+
this._joins[aliasedName] = this.helper.joinManyToOneReference(prop, fromAlias, alias, type, cond, schema);
|
|
594
768
|
}
|
|
595
769
|
if (!this._joins[aliasedName].path && path) {
|
|
596
770
|
this._joins[aliasedName].path = path;
|
|
@@ -599,34 +773,49 @@ class QueryBuilder {
|
|
|
599
773
|
}
|
|
600
774
|
prepareFields(fields, type = 'where') {
|
|
601
775
|
const ret = [];
|
|
776
|
+
const getFieldName = (name) => {
|
|
777
|
+
if (type === 'groupBy') {
|
|
778
|
+
return this.helper.mapper(name, this.type, undefined, null);
|
|
779
|
+
}
|
|
780
|
+
return this.helper.mapper(name, this.type);
|
|
781
|
+
};
|
|
602
782
|
fields.forEach(field => {
|
|
783
|
+
const rawField = core_1.RawQueryFragment.getKnownFragment(field);
|
|
784
|
+
if (rawField) {
|
|
785
|
+
const sql = this.platform.formatQuery(rawField.sql, rawField.params);
|
|
786
|
+
ret.push(this.knex.raw(sql));
|
|
787
|
+
return;
|
|
788
|
+
}
|
|
603
789
|
if (!core_1.Utils.isString(field)) {
|
|
604
|
-
|
|
790
|
+
ret.push(field);
|
|
791
|
+
return;
|
|
605
792
|
}
|
|
606
793
|
const join = Object.keys(this._joins).find(k => field === k.substring(0, k.indexOf('#')));
|
|
607
794
|
if (join && type === 'where') {
|
|
608
|
-
|
|
795
|
+
ret.push(...this.helper.mapJoinColumns(this.type ?? enums_1.QueryType.SELECT, this._joins[join]));
|
|
796
|
+
return;
|
|
609
797
|
}
|
|
610
798
|
const [a, f] = this.helper.splitField(field);
|
|
611
799
|
const prop = this.helper.getProperty(f, a);
|
|
612
800
|
/* istanbul ignore next */
|
|
613
|
-
if (prop && [core_1.
|
|
801
|
+
if (prop && [core_1.ReferenceKind.ONE_TO_MANY, core_1.ReferenceKind.MANY_TO_MANY].includes(prop.kind)) {
|
|
614
802
|
return;
|
|
615
803
|
}
|
|
616
804
|
if (prop?.embedded) {
|
|
617
|
-
const
|
|
618
|
-
|
|
805
|
+
const name = prop.embeddedPath?.join('.') ?? prop.fieldNames[0];
|
|
806
|
+
const aliased = this._aliases[a] ? `${a}.${name}` : name;
|
|
807
|
+
ret.push(getFieldName(aliased));
|
|
619
808
|
return;
|
|
620
809
|
}
|
|
621
|
-
if (prop?.
|
|
810
|
+
if (prop?.kind === core_1.ReferenceKind.EMBEDDED) {
|
|
622
811
|
if (prop.object) {
|
|
623
|
-
ret.push(
|
|
812
|
+
ret.push(getFieldName(prop.fieldNames[0]));
|
|
624
813
|
}
|
|
625
814
|
else {
|
|
626
815
|
const nest = (prop) => {
|
|
627
816
|
for (const childProp of Object.values(prop.embeddedProps)) {
|
|
628
|
-
if (childProp.fieldNames) {
|
|
629
|
-
ret.push(
|
|
817
|
+
if (childProp.fieldNames && (childProp.kind !== core_1.ReferenceKind.EMBEDDED || childProp.object) && childProp.persist !== false) {
|
|
818
|
+
ret.push(getFieldName(childProp.fieldNames[0]));
|
|
630
819
|
}
|
|
631
820
|
else {
|
|
632
821
|
nest(childProp);
|
|
@@ -637,11 +826,15 @@ class QueryBuilder {
|
|
|
637
826
|
}
|
|
638
827
|
return;
|
|
639
828
|
}
|
|
640
|
-
|
|
829
|
+
if (prop && prop.fieldNames.length > 1) {
|
|
830
|
+
ret.push(...prop.fieldNames.map(f => getFieldName(f)));
|
|
831
|
+
return;
|
|
832
|
+
}
|
|
833
|
+
ret.push(getFieldName(field));
|
|
641
834
|
});
|
|
642
835
|
const meta = this.mainAlias.metadata;
|
|
643
836
|
/* istanbul ignore next */
|
|
644
|
-
const requiresSQLConversion = meta?.props.filter(p => p.
|
|
837
|
+
const requiresSQLConversion = meta?.props.filter(p => p.hasConvertToJSValueSQL && p.persist !== false) ?? [];
|
|
645
838
|
if (this.flags.has(core_1.QueryFlag.CONVERT_CUSTOM_TYPES) && (fields.includes('*') || fields.includes(`${this.mainAlias.aliasName}.*`)) && requiresSQLConversion.length > 0) {
|
|
646
839
|
requiresSQLConversion.forEach(p => ret.push(this.helper.mapper(p.name, this.type)));
|
|
647
840
|
}
|
|
@@ -650,7 +843,7 @@ class QueryBuilder {
|
|
|
650
843
|
const cols = this.helper.mapJoinColumns(this.type ?? enums_1.QueryType.SELECT, this._joins[f]);
|
|
651
844
|
ret.push(...cols);
|
|
652
845
|
}
|
|
653
|
-
if (this._joins[f].prop.
|
|
846
|
+
if (this._joins[f].prop.kind !== core_1.ReferenceKind.ONE_TO_ONE && this._joins[f].inverseJoinColumns) {
|
|
654
847
|
this._joins[f].inverseJoinColumns.forEach(inverseJoinColumn => {
|
|
655
848
|
core_1.Utils.renameKey(this._cond, inverseJoinColumn, `${this._joins[f].alias}.${inverseJoinColumn}`);
|
|
656
849
|
});
|
|
@@ -669,10 +862,9 @@ class QueryBuilder {
|
|
|
669
862
|
}
|
|
670
863
|
if (data) {
|
|
671
864
|
if (core_1.Utils.isEntity(data)) {
|
|
672
|
-
// data = helper(data).toJSON();
|
|
673
865
|
data = this.em?.getComparator().prepareEntity(data) ?? (0, core_1.serialize)(data);
|
|
674
866
|
}
|
|
675
|
-
this._data = this.helper.processData(data, this.flags.has(core_1.QueryFlag.CONVERT_CUSTOM_TYPES));
|
|
867
|
+
this._data = this.helper.processData(data, this.flags.has(core_1.QueryFlag.CONVERT_CUSTOM_TYPES), false);
|
|
676
868
|
}
|
|
677
869
|
if (cond) {
|
|
678
870
|
this.where(cond);
|
|
@@ -682,6 +874,8 @@ class QueryBuilder {
|
|
|
682
874
|
getQueryBase() {
|
|
683
875
|
const qb = this.getKnex();
|
|
684
876
|
const schema = this.getSchema(this.mainAlias);
|
|
877
|
+
// Joined tables doesn't need to belong to the same schema as the main table
|
|
878
|
+
const joinSchema = this._schema ?? this.em?.schema ?? schema;
|
|
685
879
|
if (schema) {
|
|
686
880
|
qb.withSchema(schema);
|
|
687
881
|
}
|
|
@@ -700,12 +894,12 @@ class QueryBuilder {
|
|
|
700
894
|
else if (this.flags.has(core_1.QueryFlag.DISTINCT)) {
|
|
701
895
|
qb.distinct();
|
|
702
896
|
}
|
|
703
|
-
this.helper.processJoins(qb, this._joins,
|
|
897
|
+
this.helper.processJoins(qb, this._joins, joinSchema);
|
|
704
898
|
break;
|
|
705
899
|
case enums_1.QueryType.COUNT: {
|
|
706
900
|
const m = this.flags.has(core_1.QueryFlag.DISTINCT) ? 'countDistinct' : 'count';
|
|
707
901
|
qb[m]({ count: this._fields.map(f => this.helper.mapper(f, this.type)) });
|
|
708
|
-
this.helper.processJoins(qb, this._joins,
|
|
902
|
+
this.helper.processJoins(qb, this._joins, joinSchema);
|
|
709
903
|
break;
|
|
710
904
|
}
|
|
711
905
|
case enums_1.QueryType.INSERT:
|
|
@@ -724,6 +918,24 @@ class QueryBuilder {
|
|
|
724
918
|
}
|
|
725
919
|
return qb;
|
|
726
920
|
}
|
|
921
|
+
applyDiscriminatorCondition() {
|
|
922
|
+
const meta = this.mainAlias.metadata;
|
|
923
|
+
if (!meta?.discriminatorValue) {
|
|
924
|
+
return;
|
|
925
|
+
}
|
|
926
|
+
const types = Object.values(meta.root.discriminatorMap).map(cls => this.metadata.find(cls));
|
|
927
|
+
const children = [];
|
|
928
|
+
const lookUpChildren = (ret, type) => {
|
|
929
|
+
const children = types.filter(meta2 => meta2.extends === type);
|
|
930
|
+
children.forEach(m => lookUpChildren(ret, m.className));
|
|
931
|
+
ret.push(...children.filter(c => c.discriminatorValue));
|
|
932
|
+
return children;
|
|
933
|
+
};
|
|
934
|
+
lookUpChildren(children, meta.className);
|
|
935
|
+
this.andWhere({
|
|
936
|
+
[meta.root.discriminatorColumn]: children.length > 0 ? { $in: [meta.discriminatorValue, ...children.map(c => c.discriminatorValue)] } : meta.discriminatorValue,
|
|
937
|
+
});
|
|
938
|
+
}
|
|
727
939
|
finalize() {
|
|
728
940
|
if (this.finalized) {
|
|
729
941
|
return;
|
|
@@ -732,10 +944,11 @@ class QueryBuilder {
|
|
|
732
944
|
this.select('*');
|
|
733
945
|
}
|
|
734
946
|
const meta = this.mainAlias.metadata;
|
|
947
|
+
this.applyDiscriminatorCondition();
|
|
735
948
|
if (meta && this.flags.has(core_1.QueryFlag.AUTO_JOIN_ONE_TO_ONE_OWNER)) {
|
|
736
949
|
const relationsToPopulate = this._populate.map(({ field }) => field);
|
|
737
950
|
meta.relations
|
|
738
|
-
.filter(prop => prop.
|
|
951
|
+
.filter(prop => prop.kind === core_1.ReferenceKind.ONE_TO_ONE && !prop.owner && !relationsToPopulate.includes(prop.name))
|
|
739
952
|
.map(prop => ({ field: prop.name }))
|
|
740
953
|
.forEach(item => this._populate.push(item));
|
|
741
954
|
}
|
|
@@ -744,16 +957,14 @@ class QueryBuilder {
|
|
|
744
957
|
const aliasedField = `${fromAlias}.${fromField}`;
|
|
745
958
|
const join = Object.keys(this._joins).find(k => `${aliasedField}#${this._joins[k].alias}` === k);
|
|
746
959
|
if (join && this._joins[join] && this.helper.isOneToOneInverse(fromField)) {
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
if (this.metadata.find(field)?.pivotTable) { // pivot table entity
|
|
750
|
-
this.autoJoinPivotTable(field);
|
|
960
|
+
this._populateMap[join] = this._joins[join].alias;
|
|
961
|
+
return;
|
|
751
962
|
}
|
|
752
|
-
|
|
963
|
+
if (meta && this.helper.isOneToOneInverse(fromField)) {
|
|
753
964
|
const prop = meta.properties[fromField];
|
|
754
965
|
const alias = this.getNextAlias(prop.pivotEntity ?? prop.type);
|
|
755
966
|
const aliasedName = `${fromAlias}.${prop.name}#${alias}`;
|
|
756
|
-
this._joins[aliasedName] = this.helper.joinOneToReference(prop, this.mainAlias.aliasName, alias,
|
|
967
|
+
this._joins[aliasedName] = this.helper.joinOneToReference(prop, this.mainAlias.aliasName, alias, enums_1.JoinType.leftJoin);
|
|
757
968
|
this._populateMap[aliasedName] = this._joins[aliasedName].alias;
|
|
758
969
|
}
|
|
759
970
|
});
|
|
@@ -765,14 +976,20 @@ class QueryBuilder {
|
|
|
765
976
|
const aliased = this.knex.ref(prop.fieldNames[0]).toString();
|
|
766
977
|
return `${prop.formula(alias)} as ${aliased}`;
|
|
767
978
|
})
|
|
768
|
-
.filter(field => !this._fields.
|
|
769
|
-
|
|
979
|
+
.filter(field => !this._fields.some(f => {
|
|
980
|
+
if (f instanceof core_1.RawQueryFragment) {
|
|
981
|
+
return f.sql === field && f.params.length === 0;
|
|
982
|
+
}
|
|
983
|
+
return f === field;
|
|
984
|
+
}))
|
|
985
|
+
.forEach(field => this._fields.push((0, core_1.raw)(field)));
|
|
770
986
|
}
|
|
987
|
+
this.processPopulateWhere();
|
|
771
988
|
core_1.QueryHelper.processObjectParams(this._data);
|
|
772
989
|
core_1.QueryHelper.processObjectParams(this._cond);
|
|
773
990
|
core_1.QueryHelper.processObjectParams(this._having);
|
|
774
|
-
// automatically enable paginate flag when we detect to-many joins
|
|
775
|
-
if (!this.flags.has(core_1.QueryFlag.DISABLE_PAGINATE) && this.hasToManyJoins()) {
|
|
991
|
+
// automatically enable paginate flag when we detect to-many joins, but only if there is no `group by` clause
|
|
992
|
+
if (!this.flags.has(core_1.QueryFlag.DISABLE_PAGINATE) && this._groupBy.length === 0 && this.hasToManyJoins()) {
|
|
776
993
|
this.flags.add(core_1.QueryFlag.PAGINATE);
|
|
777
994
|
}
|
|
778
995
|
if (meta && this.flags.has(core_1.QueryFlag.PAGINATE) && (this._limit > 0 || this._offset > 0)) {
|
|
@@ -783,17 +1000,54 @@ class QueryBuilder {
|
|
|
783
1000
|
}
|
|
784
1001
|
this.finalized = true;
|
|
785
1002
|
}
|
|
1003
|
+
processPopulateWhere() {
|
|
1004
|
+
if (this._populateWhere == null || this._populateWhere === core_1.PopulateHint.ALL) {
|
|
1005
|
+
return;
|
|
1006
|
+
}
|
|
1007
|
+
const joins = Object.values(this._joins);
|
|
1008
|
+
joins.forEach(join => {
|
|
1009
|
+
join.cond_ = join.cond;
|
|
1010
|
+
join.cond = {};
|
|
1011
|
+
});
|
|
1012
|
+
const replaceOnConditions = (cond) => {
|
|
1013
|
+
Object.keys(cond).forEach(k => {
|
|
1014
|
+
if (core_1.Utils.isOperator(k)) {
|
|
1015
|
+
if (Array.isArray(cond[k])) {
|
|
1016
|
+
return cond[k].forEach((c) => replaceOnConditions(c));
|
|
1017
|
+
}
|
|
1018
|
+
return replaceOnConditions(cond[k]);
|
|
1019
|
+
}
|
|
1020
|
+
const [a] = this.helper.splitField(k);
|
|
1021
|
+
const join = joins.find(j => j.alias === a);
|
|
1022
|
+
if (join) {
|
|
1023
|
+
join.cond = { ...join.cond, [k]: cond[k] };
|
|
1024
|
+
}
|
|
1025
|
+
});
|
|
1026
|
+
};
|
|
1027
|
+
if (this._populateWhere === core_1.PopulateHint.INFER) {
|
|
1028
|
+
replaceOnConditions(this._cond);
|
|
1029
|
+
}
|
|
1030
|
+
else if (typeof this._populateWhere === 'object') {
|
|
1031
|
+
const cond = CriteriaNodeFactory_1.CriteriaNodeFactory
|
|
1032
|
+
.createNode(this.metadata, this.mainAlias.entityName, this._populateWhere)
|
|
1033
|
+
.process(this);
|
|
1034
|
+
replaceOnConditions(cond);
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
786
1037
|
hasToManyJoins() {
|
|
787
1038
|
return Object.values(this._joins).some(join => {
|
|
788
|
-
return [core_1.
|
|
1039
|
+
return [core_1.ReferenceKind.ONE_TO_MANY, core_1.ReferenceKind.MANY_TO_MANY].includes(join.prop.kind);
|
|
789
1040
|
});
|
|
790
1041
|
}
|
|
791
1042
|
wrapPaginateSubQuery(meta) {
|
|
792
1043
|
const pks = this.prepareFields(meta.primaryKeys, 'sub-query');
|
|
793
1044
|
const subQuery = this.clone().select(pks).groupBy(pks).limit(this._limit);
|
|
1045
|
+
// revert the on conditions added via populateWhere, we want to apply those only once
|
|
1046
|
+
Object.values(subQuery._joins).forEach(join => join.cond = join.cond_ ?? {});
|
|
794
1047
|
if (this._offset) {
|
|
795
1048
|
subQuery.offset(this._offset);
|
|
796
1049
|
}
|
|
1050
|
+
const addToSelect = [];
|
|
797
1051
|
if (this._orderBy.length > 0) {
|
|
798
1052
|
const orderBy = [];
|
|
799
1053
|
for (const orderMap of this._orderBy) {
|
|
@@ -801,28 +1055,38 @@ class QueryBuilder {
|
|
|
801
1055
|
const [a, f] = this.helper.splitField(field);
|
|
802
1056
|
const prop = this.helper.getProperty(f, a);
|
|
803
1057
|
const type = this.platform.castColumn(prop);
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
1058
|
+
const fieldName = this.helper.mapper(field, this.type, undefined, null);
|
|
1059
|
+
if (!prop?.persist && !prop?.formula) {
|
|
1060
|
+
addToSelect.push(fieldName);
|
|
1061
|
+
}
|
|
1062
|
+
orderBy.push({ [(0, core_1.raw)(`min(${this.knex.ref(fieldName)}${type})`)]: direction });
|
|
807
1063
|
}
|
|
808
1064
|
}
|
|
809
1065
|
subQuery.orderBy(orderBy);
|
|
810
1066
|
}
|
|
811
1067
|
subQuery.finalized = true;
|
|
812
1068
|
const knexQuery = subQuery.as(this.mainAlias.aliasName).clearSelect().select(pks);
|
|
1069
|
+
if (addToSelect.length > 0) {
|
|
1070
|
+
addToSelect.forEach(prop => {
|
|
1071
|
+
const field = this._fields.find(field => {
|
|
1072
|
+
if (typeof field === 'object' && field && '__as' in field) {
|
|
1073
|
+
return field.__as === prop;
|
|
1074
|
+
}
|
|
1075
|
+
// not perfect, but should work most of the time, ideally we should check only the alias (`... as alias`)
|
|
1076
|
+
return field.toString().includes(prop);
|
|
1077
|
+
});
|
|
1078
|
+
if (field) {
|
|
1079
|
+
knexQuery.select(field);
|
|
1080
|
+
}
|
|
1081
|
+
});
|
|
1082
|
+
}
|
|
813
1083
|
// multiple sub-queries are needed to get around mysql limitations with order by + limit + where in + group by (o.O)
|
|
814
1084
|
// https://stackoverflow.com/questions/17892762/mysql-this-version-of-mysql-doesnt-yet-support-limit-in-all-any-some-subqu
|
|
815
1085
|
const subSubQuery = this.getKnex().select(pks).from(knexQuery);
|
|
1086
|
+
subSubQuery.__raw = true; // tag it as there is now way to check via `instanceof`
|
|
816
1087
|
this._limit = undefined;
|
|
817
1088
|
this._offset = undefined;
|
|
818
|
-
const cond = this._cond;
|
|
819
1089
|
this.select(this._fields).where({ [core_1.Utils.getPrimaryKeyHash(meta.primaryKeys)]: { $in: subSubQuery } });
|
|
820
|
-
if (this._populateWhere === core_1.PopulateHint.INFER) {
|
|
821
|
-
this.andWhere(cond);
|
|
822
|
-
}
|
|
823
|
-
else if (typeof this._populateWhere === 'object') {
|
|
824
|
-
this.andWhere(this._populateWhere);
|
|
825
|
-
}
|
|
826
1090
|
}
|
|
827
1091
|
wrapModifySubQuery(meta) {
|
|
828
1092
|
const subQuery = this.clone();
|
|
@@ -836,21 +1100,10 @@ class QueryBuilder {
|
|
|
836
1100
|
[core_1.Utils.getPrimaryKeyHash(meta.primaryKeys)]: { $in: subSubQuery },
|
|
837
1101
|
});
|
|
838
1102
|
}
|
|
839
|
-
autoJoinPivotTable(field) {
|
|
840
|
-
const pivotMeta = this.metadata.find(field);
|
|
841
|
-
const owner = pivotMeta.relations[0];
|
|
842
|
-
const inverse = pivotMeta.relations[1];
|
|
843
|
-
const prop = this._cond[pivotMeta.name + '.' + owner.name] || this._orderBy[pivotMeta.name + '.' + owner.name] ? inverse : owner;
|
|
844
|
-
const pivotAlias = this.getNextAlias(pivotMeta.name);
|
|
845
|
-
this._joins[field] = this.helper.joinPivotTable(field, prop, this.mainAlias.aliasName, pivotAlias, 'leftJoin');
|
|
846
|
-
core_1.Utils.renameKey(this._cond, `${field}.${owner.name}`, core_1.Utils.getPrimaryKeyHash(owner.fieldNames.map(fieldName => `${pivotAlias}.${fieldName}`)));
|
|
847
|
-
core_1.Utils.renameKey(this._cond, `${field}.${inverse.name}`, core_1.Utils.getPrimaryKeyHash(inverse.fieldNames.map(fieldName => `${pivotAlias}.${fieldName}`)));
|
|
848
|
-
this._populateMap[field] = this._joins[field].alias;
|
|
849
|
-
}
|
|
850
1103
|
getSchema(alias) {
|
|
851
1104
|
const { metadata } = alias;
|
|
852
1105
|
const metaSchema = metadata?.schema && metadata.schema !== '*' ? metadata.schema : undefined;
|
|
853
|
-
return this._schema ?? metaSchema ?? this.em?.config.get('schema');
|
|
1106
|
+
return this._schema ?? metaSchema ?? this.em?.schema ?? this.em?.config.get('schema');
|
|
854
1107
|
}
|
|
855
1108
|
createAlias(entityName, aliasName, subQuery) {
|
|
856
1109
|
const metadata = this.metadata.find(entityName);
|
|
@@ -866,11 +1119,11 @@ class QueryBuilder {
|
|
|
866
1119
|
fromSubQuery(target, aliasName) {
|
|
867
1120
|
const subQuery = target.getKnexQuery();
|
|
868
1121
|
const { entityName } = target.mainAlias;
|
|
869
|
-
aliasName
|
|
1122
|
+
aliasName ??= this.getNextAlias(entityName);
|
|
870
1123
|
this.createMainAlias(entityName, aliasName, subQuery);
|
|
871
1124
|
}
|
|
872
1125
|
fromEntityName(entityName, aliasName) {
|
|
873
|
-
aliasName
|
|
1126
|
+
aliasName ??= this._mainAlias?.aliasName ?? this.getNextAlias(entityName);
|
|
874
1127
|
this.createMainAlias(entityName, aliasName);
|
|
875
1128
|
}
|
|
876
1129
|
createQueryBuilderHelper() {
|
|
@@ -888,6 +1141,7 @@ class QueryBuilder {
|
|
|
888
1141
|
}
|
|
889
1142
|
}
|
|
890
1143
|
/* istanbul ignore next */
|
|
1144
|
+
/** @ignore */
|
|
891
1145
|
[util_1.inspect.custom](depth) {
|
|
892
1146
|
const object = { ...this };
|
|
893
1147
|
const hidden = ['metadata', 'driver', 'context', 'platform', 'knex', 'type'];
|
|
@@ -898,6 +1152,9 @@ class QueryBuilder {
|
|
|
898
1152
|
if (this._data) {
|
|
899
1153
|
object.data = this._data;
|
|
900
1154
|
}
|
|
1155
|
+
if (this._schema) {
|
|
1156
|
+
object.schema = this._schema;
|
|
1157
|
+
}
|
|
901
1158
|
if (!core_1.Utils.isEmpty(this._cond)) {
|
|
902
1159
|
object.where = this._cond;
|
|
903
1160
|
}
|