@mikro-orm/knex 6.0.0-dev.32 → 6.0.0-dev.321

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