@mikro-orm/knex 6.0.0-dev.16 → 6.0.0-dev.160
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 -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 +17 -13
- package/query/ObjectCriteriaNode.d.ts +4 -3
- package/query/ObjectCriteriaNode.js +48 -17
- package/query/QueryBuilder.d.ts +53 -24
- package/query/QueryBuilder.js +393 -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 +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 +19 -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
|
+
});
|
|
591
|
+
}
|
|
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);
|
|
467
596
|
}
|
|
468
|
-
return
|
|
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,15 +658,22 @@ 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
|
-
clone() {
|
|
666
|
+
clone(reset) {
|
|
522
667
|
const qb = new QueryBuilder(this.mainAlias.entityName, this.metadata, this.driver, this.context, this.mainAlias.aliasName, this.connectionType, this.em);
|
|
668
|
+
if (reset) {
|
|
669
|
+
return qb;
|
|
670
|
+
}
|
|
523
671
|
Object.assign(qb, this);
|
|
524
672
|
// clone array/object properties
|
|
525
673
|
const properties = [
|
|
526
674
|
'flags', '_populate', '_populateWhere', '_populateMap', '_joins', '_joinedProps', '_cond', '_data', '_orderBy',
|
|
527
|
-
'_schema', '_indexHint', '_cache', 'subQueries', 'lockMode', 'lockTables',
|
|
675
|
+
'_schema', '_indexHint', '_cache', 'subQueries', 'lockMode', 'lockTables', '_groupBy', '_having', '_returning',
|
|
676
|
+
'_comments', '_hintComments',
|
|
528
677
|
];
|
|
529
678
|
properties.forEach(prop => qb[prop] = core_1.Utils.copy(this[prop]));
|
|
530
679
|
/* istanbul ignore else */
|
|
@@ -552,14 +701,42 @@ class QueryBuilder {
|
|
|
552
701
|
}
|
|
553
702
|
return qb;
|
|
554
703
|
}
|
|
555
|
-
joinReference(field, alias, cond, type, path) {
|
|
704
|
+
joinReference(field, alias, cond, type, path, schema, subquery) {
|
|
556
705
|
this.ensureNotFinalized();
|
|
706
|
+
if (typeof field === 'object') {
|
|
707
|
+
const prop = {
|
|
708
|
+
name: '__subquery__',
|
|
709
|
+
kind: core_1.ReferenceKind.MANY_TO_ONE,
|
|
710
|
+
};
|
|
711
|
+
if (field instanceof QueryBuilder) {
|
|
712
|
+
prop.type = field.mainAlias.entityName;
|
|
713
|
+
prop.targetMeta = field.mainAlias.metadata;
|
|
714
|
+
field = field.getKnexQuery();
|
|
715
|
+
}
|
|
716
|
+
this._joins[`${this.alias}.${prop.name}#${alias}`] = {
|
|
717
|
+
prop,
|
|
718
|
+
alias,
|
|
719
|
+
type,
|
|
720
|
+
cond,
|
|
721
|
+
schema,
|
|
722
|
+
subquery: field.toString(),
|
|
723
|
+
ownerAlias: this.alias,
|
|
724
|
+
};
|
|
725
|
+
return prop;
|
|
726
|
+
}
|
|
727
|
+
if (!subquery && type.includes('lateral')) {
|
|
728
|
+
throw new Error(`Lateral join can be used only with a sub-query.`);
|
|
729
|
+
}
|
|
557
730
|
const [fromAlias, fromField] = this.helper.splitField(field);
|
|
558
|
-
const
|
|
731
|
+
const q = (str) => `'${str}'`;
|
|
732
|
+
if (!this._aliases[fromAlias]) {
|
|
733
|
+
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(', ')}.`);
|
|
734
|
+
}
|
|
735
|
+
const entityName = this._aliases[fromAlias].entityName;
|
|
559
736
|
const meta = this.metadata.get(entityName);
|
|
560
737
|
const prop = meta.properties[fromField];
|
|
561
738
|
if (!prop) {
|
|
562
|
-
throw new Error(`Trying to join ${field}, but ${fromField} is not a defined relation on ${meta.className}
|
|
739
|
+
throw new Error(`Trying to join ${q(field)}, but ${q(fromField)} is not a defined relation on ${meta.className}.`);
|
|
563
740
|
}
|
|
564
741
|
this.createAlias(prop.type, alias);
|
|
565
742
|
cond = core_1.QueryHelper.processWhere({
|
|
@@ -571,26 +748,26 @@ class QueryBuilder {
|
|
|
571
748
|
aliased: !this.type || [enums_1.QueryType.SELECT, enums_1.QueryType.COUNT].includes(this.type),
|
|
572
749
|
});
|
|
573
750
|
let aliasedName = `${fromAlias}.${prop.name}#${alias}`;
|
|
574
|
-
path
|
|
575
|
-
if (prop.
|
|
576
|
-
this._joins[aliasedName] = this.helper.joinOneToReference(prop, fromAlias, alias, type, cond);
|
|
751
|
+
path ??= `${(Object.values(this._joins).find(j => j.alias === fromAlias)?.path ?? entityName)}.${prop.name}`;
|
|
752
|
+
if (prop.kind === core_1.ReferenceKind.ONE_TO_MANY) {
|
|
753
|
+
this._joins[aliasedName] = this.helper.joinOneToReference(prop, fromAlias, alias, type, cond, schema);
|
|
577
754
|
}
|
|
578
|
-
else if (prop.
|
|
755
|
+
else if (prop.kind === core_1.ReferenceKind.MANY_TO_MANY) {
|
|
579
756
|
let pivotAlias = alias;
|
|
580
|
-
if (type !==
|
|
757
|
+
if (type !== enums_1.JoinType.pivotJoin) {
|
|
581
758
|
const oldPivotAlias = this.getAliasForJoinPath(path + '[pivot]');
|
|
582
759
|
pivotAlias = oldPivotAlias ?? this.getNextAlias(prop.pivotEntity);
|
|
583
760
|
aliasedName = `${fromAlias}.${prop.name}#${pivotAlias}`;
|
|
584
761
|
}
|
|
585
|
-
const joins = this.helper.joinManyToManyReference(prop, fromAlias, alias, pivotAlias, type, cond, path);
|
|
762
|
+
const joins = this.helper.joinManyToManyReference(prop, fromAlias, alias, pivotAlias, type, cond, path, schema);
|
|
586
763
|
Object.assign(this._joins, joins);
|
|
587
764
|
this.createAlias(prop.pivotEntity, pivotAlias);
|
|
588
765
|
}
|
|
589
|
-
else if (prop.
|
|
590
|
-
this._joins[aliasedName] = this.helper.joinOneToReference(prop, fromAlias, alias, type, cond);
|
|
766
|
+
else if (prop.kind === core_1.ReferenceKind.ONE_TO_ONE) {
|
|
767
|
+
this._joins[aliasedName] = this.helper.joinOneToReference(prop, fromAlias, alias, type, cond, schema);
|
|
591
768
|
}
|
|
592
769
|
else { // MANY_TO_ONE
|
|
593
|
-
this._joins[aliasedName] = this.helper.joinManyToOneReference(prop, fromAlias, alias, type, cond);
|
|
770
|
+
this._joins[aliasedName] = this.helper.joinManyToOneReference(prop, fromAlias, alias, type, cond, schema);
|
|
594
771
|
}
|
|
595
772
|
if (!this._joins[aliasedName].path && path) {
|
|
596
773
|
this._joins[aliasedName].path = path;
|
|
@@ -599,34 +776,49 @@ class QueryBuilder {
|
|
|
599
776
|
}
|
|
600
777
|
prepareFields(fields, type = 'where') {
|
|
601
778
|
const ret = [];
|
|
779
|
+
const getFieldName = (name) => {
|
|
780
|
+
if (type === 'groupBy') {
|
|
781
|
+
return this.helper.mapper(name, this.type, undefined, null);
|
|
782
|
+
}
|
|
783
|
+
return this.helper.mapper(name, this.type);
|
|
784
|
+
};
|
|
602
785
|
fields.forEach(field => {
|
|
786
|
+
const rawField = core_1.RawQueryFragment.getKnownFragment(field);
|
|
787
|
+
if (rawField) {
|
|
788
|
+
const sql = this.platform.formatQuery(rawField.sql, rawField.params);
|
|
789
|
+
ret.push(this.knex.raw(sql));
|
|
790
|
+
return;
|
|
791
|
+
}
|
|
603
792
|
if (!core_1.Utils.isString(field)) {
|
|
604
|
-
|
|
793
|
+
ret.push(field);
|
|
794
|
+
return;
|
|
605
795
|
}
|
|
606
796
|
const join = Object.keys(this._joins).find(k => field === k.substring(0, k.indexOf('#')));
|
|
607
797
|
if (join && type === 'where') {
|
|
608
|
-
|
|
798
|
+
ret.push(...this.helper.mapJoinColumns(this.type ?? enums_1.QueryType.SELECT, this._joins[join]));
|
|
799
|
+
return;
|
|
609
800
|
}
|
|
610
801
|
const [a, f] = this.helper.splitField(field);
|
|
611
802
|
const prop = this.helper.getProperty(f, a);
|
|
612
803
|
/* istanbul ignore next */
|
|
613
|
-
if (prop && [core_1.
|
|
804
|
+
if (prop && [core_1.ReferenceKind.ONE_TO_MANY, core_1.ReferenceKind.MANY_TO_MANY].includes(prop.kind)) {
|
|
614
805
|
return;
|
|
615
806
|
}
|
|
616
807
|
if (prop?.embedded) {
|
|
617
|
-
const
|
|
618
|
-
|
|
808
|
+
const name = prop.embeddedPath?.join('.') ?? prop.fieldNames[0];
|
|
809
|
+
const aliased = this._aliases[a] ? `${a}.${name}` : name;
|
|
810
|
+
ret.push(getFieldName(aliased));
|
|
619
811
|
return;
|
|
620
812
|
}
|
|
621
|
-
if (prop?.
|
|
813
|
+
if (prop?.kind === core_1.ReferenceKind.EMBEDDED) {
|
|
622
814
|
if (prop.object) {
|
|
623
|
-
ret.push(
|
|
815
|
+
ret.push(getFieldName(prop.fieldNames[0]));
|
|
624
816
|
}
|
|
625
817
|
else {
|
|
626
818
|
const nest = (prop) => {
|
|
627
819
|
for (const childProp of Object.values(prop.embeddedProps)) {
|
|
628
|
-
if (childProp.fieldNames) {
|
|
629
|
-
ret.push(
|
|
820
|
+
if (childProp.fieldNames && (childProp.kind !== core_1.ReferenceKind.EMBEDDED || childProp.object) && childProp.persist !== false) {
|
|
821
|
+
ret.push(getFieldName(childProp.fieldNames[0]));
|
|
630
822
|
}
|
|
631
823
|
else {
|
|
632
824
|
nest(childProp);
|
|
@@ -637,11 +829,15 @@ class QueryBuilder {
|
|
|
637
829
|
}
|
|
638
830
|
return;
|
|
639
831
|
}
|
|
640
|
-
|
|
832
|
+
if (prop && prop.fieldNames.length > 1) {
|
|
833
|
+
ret.push(...prop.fieldNames.map(f => getFieldName(f)));
|
|
834
|
+
return;
|
|
835
|
+
}
|
|
836
|
+
ret.push(getFieldName(field));
|
|
641
837
|
});
|
|
642
838
|
const meta = this.mainAlias.metadata;
|
|
643
839
|
/* istanbul ignore next */
|
|
644
|
-
const requiresSQLConversion = meta?.props.filter(p => p.
|
|
840
|
+
const requiresSQLConversion = meta?.props.filter(p => p.hasConvertToJSValueSQL && p.persist !== false) ?? [];
|
|
645
841
|
if (this.flags.has(core_1.QueryFlag.CONVERT_CUSTOM_TYPES) && (fields.includes('*') || fields.includes(`${this.mainAlias.aliasName}.*`)) && requiresSQLConversion.length > 0) {
|
|
646
842
|
requiresSQLConversion.forEach(p => ret.push(this.helper.mapper(p.name, this.type)));
|
|
647
843
|
}
|
|
@@ -650,7 +846,7 @@ class QueryBuilder {
|
|
|
650
846
|
const cols = this.helper.mapJoinColumns(this.type ?? enums_1.QueryType.SELECT, this._joins[f]);
|
|
651
847
|
ret.push(...cols);
|
|
652
848
|
}
|
|
653
|
-
if (this._joins[f].prop.
|
|
849
|
+
if (this._joins[f].prop.kind !== core_1.ReferenceKind.ONE_TO_ONE && this._joins[f].inverseJoinColumns) {
|
|
654
850
|
this._joins[f].inverseJoinColumns.forEach(inverseJoinColumn => {
|
|
655
851
|
core_1.Utils.renameKey(this._cond, inverseJoinColumn, `${this._joins[f].alias}.${inverseJoinColumn}`);
|
|
656
852
|
});
|
|
@@ -669,10 +865,9 @@ class QueryBuilder {
|
|
|
669
865
|
}
|
|
670
866
|
if (data) {
|
|
671
867
|
if (core_1.Utils.isEntity(data)) {
|
|
672
|
-
// data = helper(data).toJSON();
|
|
673
868
|
data = this.em?.getComparator().prepareEntity(data) ?? (0, core_1.serialize)(data);
|
|
674
869
|
}
|
|
675
|
-
this._data = this.helper.processData(data, this.flags.has(core_1.QueryFlag.CONVERT_CUSTOM_TYPES));
|
|
870
|
+
this._data = this.helper.processData(data, this.flags.has(core_1.QueryFlag.CONVERT_CUSTOM_TYPES), false);
|
|
676
871
|
}
|
|
677
872
|
if (cond) {
|
|
678
873
|
this.where(cond);
|
|
@@ -682,6 +877,8 @@ class QueryBuilder {
|
|
|
682
877
|
getQueryBase() {
|
|
683
878
|
const qb = this.getKnex();
|
|
684
879
|
const schema = this.getSchema(this.mainAlias);
|
|
880
|
+
// Joined tables doesn't need to belong to the same schema as the main table
|
|
881
|
+
const joinSchema = this._schema ?? this.em?.schema ?? schema;
|
|
685
882
|
if (schema) {
|
|
686
883
|
qb.withSchema(schema);
|
|
687
884
|
}
|
|
@@ -700,12 +897,12 @@ class QueryBuilder {
|
|
|
700
897
|
else if (this.flags.has(core_1.QueryFlag.DISTINCT)) {
|
|
701
898
|
qb.distinct();
|
|
702
899
|
}
|
|
703
|
-
this.helper.processJoins(qb, this._joins,
|
|
900
|
+
this.helper.processJoins(qb, this._joins, joinSchema);
|
|
704
901
|
break;
|
|
705
902
|
case enums_1.QueryType.COUNT: {
|
|
706
903
|
const m = this.flags.has(core_1.QueryFlag.DISTINCT) ? 'countDistinct' : 'count';
|
|
707
904
|
qb[m]({ count: this._fields.map(f => this.helper.mapper(f, this.type)) });
|
|
708
|
-
this.helper.processJoins(qb, this._joins,
|
|
905
|
+
this.helper.processJoins(qb, this._joins, joinSchema);
|
|
709
906
|
break;
|
|
710
907
|
}
|
|
711
908
|
case enums_1.QueryType.INSERT:
|
|
@@ -724,6 +921,24 @@ class QueryBuilder {
|
|
|
724
921
|
}
|
|
725
922
|
return qb;
|
|
726
923
|
}
|
|
924
|
+
applyDiscriminatorCondition() {
|
|
925
|
+
const meta = this.mainAlias.metadata;
|
|
926
|
+
if (!meta?.discriminatorValue) {
|
|
927
|
+
return;
|
|
928
|
+
}
|
|
929
|
+
const types = Object.values(meta.root.discriminatorMap).map(cls => this.metadata.find(cls));
|
|
930
|
+
const children = [];
|
|
931
|
+
const lookUpChildren = (ret, type) => {
|
|
932
|
+
const children = types.filter(meta2 => meta2.extends === type);
|
|
933
|
+
children.forEach(m => lookUpChildren(ret, m.className));
|
|
934
|
+
ret.push(...children.filter(c => c.discriminatorValue));
|
|
935
|
+
return children;
|
|
936
|
+
};
|
|
937
|
+
lookUpChildren(children, meta.className);
|
|
938
|
+
this.andWhere({
|
|
939
|
+
[meta.root.discriminatorColumn]: children.length > 0 ? { $in: [meta.discriminatorValue, ...children.map(c => c.discriminatorValue)] } : meta.discriminatorValue,
|
|
940
|
+
});
|
|
941
|
+
}
|
|
727
942
|
finalize() {
|
|
728
943
|
if (this.finalized) {
|
|
729
944
|
return;
|
|
@@ -732,10 +947,11 @@ class QueryBuilder {
|
|
|
732
947
|
this.select('*');
|
|
733
948
|
}
|
|
734
949
|
const meta = this.mainAlias.metadata;
|
|
950
|
+
this.applyDiscriminatorCondition();
|
|
735
951
|
if (meta && this.flags.has(core_1.QueryFlag.AUTO_JOIN_ONE_TO_ONE_OWNER)) {
|
|
736
952
|
const relationsToPopulate = this._populate.map(({ field }) => field);
|
|
737
953
|
meta.relations
|
|
738
|
-
.filter(prop => prop.
|
|
954
|
+
.filter(prop => prop.kind === core_1.ReferenceKind.ONE_TO_ONE && !prop.owner && !relationsToPopulate.includes(prop.name))
|
|
739
955
|
.map(prop => ({ field: prop.name }))
|
|
740
956
|
.forEach(item => this._populate.push(item));
|
|
741
957
|
}
|
|
@@ -744,16 +960,14 @@ class QueryBuilder {
|
|
|
744
960
|
const aliasedField = `${fromAlias}.${fromField}`;
|
|
745
961
|
const join = Object.keys(this._joins).find(k => `${aliasedField}#${this._joins[k].alias}` === k);
|
|
746
962
|
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);
|
|
963
|
+
this._populateMap[join] = this._joins[join].alias;
|
|
964
|
+
return;
|
|
751
965
|
}
|
|
752
|
-
|
|
966
|
+
if (meta && this.helper.isOneToOneInverse(fromField)) {
|
|
753
967
|
const prop = meta.properties[fromField];
|
|
754
968
|
const alias = this.getNextAlias(prop.pivotEntity ?? prop.type);
|
|
755
969
|
const aliasedName = `${fromAlias}.${prop.name}#${alias}`;
|
|
756
|
-
this._joins[aliasedName] = this.helper.joinOneToReference(prop, this.mainAlias.aliasName, alias,
|
|
970
|
+
this._joins[aliasedName] = this.helper.joinOneToReference(prop, this.mainAlias.aliasName, alias, enums_1.JoinType.leftJoin);
|
|
757
971
|
this._populateMap[aliasedName] = this._joins[aliasedName].alias;
|
|
758
972
|
}
|
|
759
973
|
});
|
|
@@ -765,14 +979,20 @@ class QueryBuilder {
|
|
|
765
979
|
const aliased = this.knex.ref(prop.fieldNames[0]).toString();
|
|
766
980
|
return `${prop.formula(alias)} as ${aliased}`;
|
|
767
981
|
})
|
|
768
|
-
.filter(field => !this._fields.
|
|
769
|
-
|
|
982
|
+
.filter(field => !this._fields.some(f => {
|
|
983
|
+
if (f instanceof core_1.RawQueryFragment) {
|
|
984
|
+
return f.sql === field && f.params.length === 0;
|
|
985
|
+
}
|
|
986
|
+
return f === field;
|
|
987
|
+
}))
|
|
988
|
+
.forEach(field => this._fields.push((0, core_1.raw)(field)));
|
|
770
989
|
}
|
|
990
|
+
this.processPopulateWhere();
|
|
771
991
|
core_1.QueryHelper.processObjectParams(this._data);
|
|
772
992
|
core_1.QueryHelper.processObjectParams(this._cond);
|
|
773
993
|
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()) {
|
|
994
|
+
// automatically enable paginate flag when we detect to-many joins, but only if there is no `group by` clause
|
|
995
|
+
if (!this.flags.has(core_1.QueryFlag.DISABLE_PAGINATE) && this._groupBy.length === 0 && this.hasToManyJoins()) {
|
|
776
996
|
this.flags.add(core_1.QueryFlag.PAGINATE);
|
|
777
997
|
}
|
|
778
998
|
if (meta && this.flags.has(core_1.QueryFlag.PAGINATE) && (this._limit > 0 || this._offset > 0)) {
|
|
@@ -783,17 +1003,54 @@ class QueryBuilder {
|
|
|
783
1003
|
}
|
|
784
1004
|
this.finalized = true;
|
|
785
1005
|
}
|
|
1006
|
+
processPopulateWhere() {
|
|
1007
|
+
if (this._populateWhere == null || this._populateWhere === core_1.PopulateHint.ALL) {
|
|
1008
|
+
return;
|
|
1009
|
+
}
|
|
1010
|
+
const joins = Object.values(this._joins);
|
|
1011
|
+
joins.forEach(join => {
|
|
1012
|
+
join.cond_ = join.cond;
|
|
1013
|
+
join.cond = {};
|
|
1014
|
+
});
|
|
1015
|
+
const replaceOnConditions = (cond) => {
|
|
1016
|
+
Object.keys(cond).forEach(k => {
|
|
1017
|
+
if (core_1.Utils.isOperator(k)) {
|
|
1018
|
+
if (Array.isArray(cond[k])) {
|
|
1019
|
+
return cond[k].forEach((c) => replaceOnConditions(c));
|
|
1020
|
+
}
|
|
1021
|
+
return replaceOnConditions(cond[k]);
|
|
1022
|
+
}
|
|
1023
|
+
const [a] = this.helper.splitField(k);
|
|
1024
|
+
const join = joins.find(j => j.alias === a);
|
|
1025
|
+
if (join) {
|
|
1026
|
+
join.cond = { ...join.cond, [k]: cond[k] };
|
|
1027
|
+
}
|
|
1028
|
+
});
|
|
1029
|
+
};
|
|
1030
|
+
if (this._populateWhere === core_1.PopulateHint.INFER) {
|
|
1031
|
+
replaceOnConditions(this._cond);
|
|
1032
|
+
}
|
|
1033
|
+
else if (typeof this._populateWhere === 'object') {
|
|
1034
|
+
const cond = CriteriaNodeFactory_1.CriteriaNodeFactory
|
|
1035
|
+
.createNode(this.metadata, this.mainAlias.entityName, this._populateWhere)
|
|
1036
|
+
.process(this);
|
|
1037
|
+
replaceOnConditions(cond);
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
786
1040
|
hasToManyJoins() {
|
|
787
1041
|
return Object.values(this._joins).some(join => {
|
|
788
|
-
return [core_1.
|
|
1042
|
+
return [core_1.ReferenceKind.ONE_TO_MANY, core_1.ReferenceKind.MANY_TO_MANY].includes(join.prop.kind);
|
|
789
1043
|
});
|
|
790
1044
|
}
|
|
791
1045
|
wrapPaginateSubQuery(meta) {
|
|
792
1046
|
const pks = this.prepareFields(meta.primaryKeys, 'sub-query');
|
|
793
1047
|
const subQuery = this.clone().select(pks).groupBy(pks).limit(this._limit);
|
|
1048
|
+
// revert the on conditions added via populateWhere, we want to apply those only once
|
|
1049
|
+
Object.values(subQuery._joins).forEach(join => join.cond = join.cond_ ?? {});
|
|
794
1050
|
if (this._offset) {
|
|
795
1051
|
subQuery.offset(this._offset);
|
|
796
1052
|
}
|
|
1053
|
+
const addToSelect = [];
|
|
797
1054
|
if (this._orderBy.length > 0) {
|
|
798
1055
|
const orderBy = [];
|
|
799
1056
|
for (const orderMap of this._orderBy) {
|
|
@@ -801,28 +1058,38 @@ class QueryBuilder {
|
|
|
801
1058
|
const [a, f] = this.helper.splitField(field);
|
|
802
1059
|
const prop = this.helper.getProperty(f, a);
|
|
803
1060
|
const type = this.platform.castColumn(prop);
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
1061
|
+
const fieldName = this.helper.mapper(field, this.type, undefined, null);
|
|
1062
|
+
if (!prop?.persist && !prop?.formula) {
|
|
1063
|
+
addToSelect.push(fieldName);
|
|
1064
|
+
}
|
|
1065
|
+
orderBy.push({ [(0, core_1.raw)(`min(${this.knex.ref(fieldName)}${type})`)]: direction });
|
|
807
1066
|
}
|
|
808
1067
|
}
|
|
809
1068
|
subQuery.orderBy(orderBy);
|
|
810
1069
|
}
|
|
811
1070
|
subQuery.finalized = true;
|
|
812
1071
|
const knexQuery = subQuery.as(this.mainAlias.aliasName).clearSelect().select(pks);
|
|
1072
|
+
if (addToSelect.length > 0) {
|
|
1073
|
+
addToSelect.forEach(prop => {
|
|
1074
|
+
const field = this._fields.find(field => {
|
|
1075
|
+
if (typeof field === 'object' && field && '__as' in field) {
|
|
1076
|
+
return field.__as === prop;
|
|
1077
|
+
}
|
|
1078
|
+
// not perfect, but should work most of the time, ideally we should check only the alias (`... as alias`)
|
|
1079
|
+
return field.toString().includes(prop);
|
|
1080
|
+
});
|
|
1081
|
+
if (field) {
|
|
1082
|
+
knexQuery.select(field);
|
|
1083
|
+
}
|
|
1084
|
+
});
|
|
1085
|
+
}
|
|
813
1086
|
// multiple sub-queries are needed to get around mysql limitations with order by + limit + where in + group by (o.O)
|
|
814
1087
|
// https://stackoverflow.com/questions/17892762/mysql-this-version-of-mysql-doesnt-yet-support-limit-in-all-any-some-subqu
|
|
815
1088
|
const subSubQuery = this.getKnex().select(pks).from(knexQuery);
|
|
1089
|
+
subSubQuery.__raw = true; // tag it as there is now way to check via `instanceof`
|
|
816
1090
|
this._limit = undefined;
|
|
817
1091
|
this._offset = undefined;
|
|
818
|
-
const cond = this._cond;
|
|
819
1092
|
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
1093
|
}
|
|
827
1094
|
wrapModifySubQuery(meta) {
|
|
828
1095
|
const subQuery = this.clone();
|
|
@@ -836,21 +1103,10 @@ class QueryBuilder {
|
|
|
836
1103
|
[core_1.Utils.getPrimaryKeyHash(meta.primaryKeys)]: { $in: subSubQuery },
|
|
837
1104
|
});
|
|
838
1105
|
}
|
|
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
1106
|
getSchema(alias) {
|
|
851
1107
|
const { metadata } = alias;
|
|
852
1108
|
const metaSchema = metadata?.schema && metadata.schema !== '*' ? metadata.schema : undefined;
|
|
853
|
-
return this._schema ?? metaSchema ?? this.em?.config.get('schema');
|
|
1109
|
+
return this._schema ?? metaSchema ?? this.em?.schema ?? this.em?.config.get('schema');
|
|
854
1110
|
}
|
|
855
1111
|
createAlias(entityName, aliasName, subQuery) {
|
|
856
1112
|
const metadata = this.metadata.find(entityName);
|
|
@@ -866,11 +1122,11 @@ class QueryBuilder {
|
|
|
866
1122
|
fromSubQuery(target, aliasName) {
|
|
867
1123
|
const subQuery = target.getKnexQuery();
|
|
868
1124
|
const { entityName } = target.mainAlias;
|
|
869
|
-
aliasName
|
|
1125
|
+
aliasName ??= this.getNextAlias(entityName);
|
|
870
1126
|
this.createMainAlias(entityName, aliasName, subQuery);
|
|
871
1127
|
}
|
|
872
1128
|
fromEntityName(entityName, aliasName) {
|
|
873
|
-
aliasName
|
|
1129
|
+
aliasName ??= this._mainAlias?.aliasName ?? this.getNextAlias(entityName);
|
|
874
1130
|
this.createMainAlias(entityName, aliasName);
|
|
875
1131
|
}
|
|
876
1132
|
createQueryBuilderHelper() {
|
|
@@ -888,6 +1144,7 @@ class QueryBuilder {
|
|
|
888
1144
|
}
|
|
889
1145
|
}
|
|
890
1146
|
/* istanbul ignore next */
|
|
1147
|
+
/** @ignore */
|
|
891
1148
|
[util_1.inspect.custom](depth) {
|
|
892
1149
|
const object = { ...this };
|
|
893
1150
|
const hidden = ['metadata', 'driver', 'context', 'platform', 'knex', 'type'];
|
|
@@ -898,6 +1155,9 @@ class QueryBuilder {
|
|
|
898
1155
|
if (this._data) {
|
|
899
1156
|
object.data = this._data;
|
|
900
1157
|
}
|
|
1158
|
+
if (this._schema) {
|
|
1159
|
+
object.schema = this._schema;
|
|
1160
|
+
}
|
|
901
1161
|
if (!core_1.Utils.isEmpty(this._cond)) {
|
|
902
1162
|
object.where = this._cond;
|
|
903
1163
|
}
|