@mikro-orm/knex 6.0.0-dev.31 → 6.0.0-dev.311

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 +47 -17
  3. package/AbstractSqlDriver.d.ts +30 -24
  4. package/AbstractSqlDriver.js +604 -266
  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 +58 -26
  27. package/query/QueryBuilder.js +445 -142
  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 +3 -4
  43. package/schema/SqlSchemaGenerator.js +66 -23
  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);
153
211
  }
154
- getFieldsForJoinedLoad(prop, alias) {
212
+ innerJoinLateralAndSelect(field, alias, cond = {}, fields, schema) {
213
+ return this.joinAndSelect(field, alias, cond, enums_1.JoinType.innerJoinLateral, undefined, fields, schema);
214
+ }
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) {
@@ -355,59 +459,105 @@ class QueryBuilder {
355
459
  getKnexQuery() {
356
460
  this.finalize();
357
461
  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);
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
- return 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);
472
+ return;
365
473
  }
366
474
  }, this._orderBy);
367
475
  core_1.Utils.runIfNotEmpty(() => qb.limit(this._limit), this._limit != null);
368
476
  core_1.Utils.runIfNotEmpty(() => qb.offset(this._offset), this._offset);
369
- 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);
370
480
  if (this.type === enums_1.QueryType.TRUNCATE && this.platform.usesCascadeStatement()) {
371
481
  return this.knex.raw(qb.toSQL().toNative().sql + ' cascade');
372
482
  }
373
483
  if (this.lockMode) {
374
484
  this.helper.getLockSQL(qb, this.lockMode, this.lockTables);
375
485
  }
376
- 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);
377
487
  return qb;
378
488
  }
379
489
  /**
380
490
  * Returns the query with parameters as wildcards.
381
491
  */
382
492
  getQuery() {
383
- 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 };
384
503
  }
385
504
  /**
386
505
  * Returns the list of all parameters for this query.
387
506
  */
388
507
  getParams() {
389
- return this.getKnexQuery().toSQL().toNative().bindings;
508
+ return this.toQuery().params;
390
509
  }
391
510
  /**
392
511
  * Returns raw interpolated query string with all the parameters inlined.
393
512
  */
394
513
  getFormattedQuery() {
395
- const query = this.getKnexQuery().toSQL();
514
+ const query = this.toQuery()._sql;
396
515
  return this.platform.formatQuery(query.sql, query.bindings);
397
516
  }
398
517
  /**
399
518
  * @internal
400
519
  */
401
- getAliasForJoinPath(path) {
520
+ getAliasForJoinPath(path, options) {
402
521
  if (!path || path === this.mainAlias.entityName) {
403
522
  return this.mainAlias.aliasName;
404
523
  }
405
- const join = Object.values(this._joins).find(j => j.path === path);
406
- if (path.endsWith('[pivot]') && join) {
524
+ const join = typeof path === 'string' ? this.getJoinForPath(path, options) : path;
525
+ if (join?.path?.endsWith('[pivot]')) {
407
526
  return join.alias;
408
527
  }
409
528
  return join?.inverseAlias || join?.alias;
410
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
+ }
411
561
  /**
412
562
  * @internal
413
563
  */
@@ -424,7 +574,10 @@ class QueryBuilder {
424
574
  * Executes this QB and returns the raw results, mapped to the property names (unless disabled via last parameter).
425
575
  * Use `method` to specify what kind of result you want to get (array/single/meta).
426
576
  */
427
- 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;
428
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)) {
429
582
  this.connectionType = 'write';
430
583
  }
@@ -433,21 +586,33 @@ class QueryBuilder {
433
586
  if (cached?.data) {
434
587
  return cached.data;
435
588
  }
436
- const type = this.connectionType || (method === 'run' ? 'write' : 'read');
437
- 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);
438
592
  const meta = this.mainAlias.metadata;
439
- if (!mapResults || !meta) {
593
+ if (!options.mapResults || !meta) {
440
594
  await this.em?.storeCache(this._cache, cached, res);
441
595
  return res;
442
596
  }
443
- 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)) {
444
603
  const map = {};
445
- const mapped = res.map(r => this.driver.mapResult(r, meta, this._populate, this, map));
446
- await this.em?.storeCache(this._cache, cached, mapped);
447
- 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)];
448
611
  }
449
- const mapped = this.driver.mapResult(res, meta, this._populate, this);
450
612
  await this.em?.storeCache(this._cache, cached, mapped);
613
+ if (method === 'get') {
614
+ return mapped[0];
615
+ }
451
616
  return mapped;
452
617
  }
453
618
  /**
@@ -461,11 +626,29 @@ class QueryBuilder {
461
626
  */
462
627
  async getResultList() {
463
628
  await this.em.tryFlush(this.mainAlias.entityName, { flushMode: this.flushMode });
464
- let res = await this.execute('all', true);
465
- if (this._joinedProps.size > 0) {
466
- 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
+ });
467
645
  }
468
- 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;
469
652
  }
470
653
  /**
471
654
  * Executes the query, returning the first result or null
@@ -483,7 +666,7 @@ class QueryBuilder {
483
666
  res = await this.execute('get', false);
484
667
  }
485
668
  else {
486
- const qb = this.clone();
669
+ const qb = this.type === undefined ? this : this.clone();
487
670
  qb.count(field, distinct ?? qb.hasToManyJoins()).limit(undefined).offset(undefined).orderBy([]);
488
671
  res = await qb.execute('get', false);
489
672
  }
@@ -502,7 +685,11 @@ class QueryBuilder {
502
685
  * Provides promise-like interface so we can await the QB instance.
503
686
  */
504
687
  then(onfulfilled, onrejected) {
505
- 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) {
506
693
  case enums_1.QueryType.INSERT:
507
694
  case enums_1.QueryType.UPDATE:
508
695
  case enums_1.QueryType.DELETE:
@@ -525,15 +712,22 @@ class QueryBuilder {
525
712
  /* istanbul ignore next */
526
713
  alias = meta?.properties[f]?.fieldNames[0] ?? alias;
527
714
  }
528
- 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;
529
719
  }
530
- clone() {
720
+ clone(reset) {
531
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
+ }
532
725
  Object.assign(qb, this);
533
726
  // clone array/object properties
534
727
  const properties = [
535
- 'flags', '_populate', '_populateWhere', '_populateMap', '_joins', '_joinedProps', '_cond', '_data', '_orderBy',
536
- '_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',
537
731
  ];
538
732
  properties.forEach(prop => qb[prop] = core_1.Utils.copy(this[prop]));
539
733
  /* istanbul ignore else */
@@ -561,14 +755,42 @@ class QueryBuilder {
561
755
  }
562
756
  return qb;
563
757
  }
564
- joinReference(field, alias, cond, type, path) {
758
+ joinReference(field, alias, cond, type, path, schema, subquery) {
565
759
  this.ensureNotFinalized();
760
+ if (typeof field === 'object') {
761
+ const prop = {
762
+ name: '__subquery__',
763
+ kind: core_1.ReferenceKind.MANY_TO_ONE,
764
+ };
765
+ if (field instanceof QueryBuilder) {
766
+ prop.type = field.mainAlias.entityName;
767
+ prop.targetMeta = field.mainAlias.metadata;
768
+ field = field.getKnexQuery();
769
+ }
770
+ this._joins[`${this.alias}.${prop.name}#${alias}`] = {
771
+ prop,
772
+ alias,
773
+ type,
774
+ cond,
775
+ schema,
776
+ subquery: field.toString(),
777
+ ownerAlias: this.alias,
778
+ };
779
+ return prop;
780
+ }
781
+ if (!subquery && type.includes('lateral')) {
782
+ throw new Error(`Lateral join can be used only with a sub-query.`);
783
+ }
566
784
  const [fromAlias, fromField] = this.helper.splitField(field);
567
- const entityName = this._aliases[fromAlias]?.entityName;
785
+ const q = (str) => `'${str}'`;
786
+ if (!this._aliases[fromAlias]) {
787
+ 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(', ')}.`);
788
+ }
789
+ const entityName = this._aliases[fromAlias].entityName;
568
790
  const meta = this.metadata.get(entityName);
569
791
  const prop = meta.properties[fromField];
570
792
  if (!prop) {
571
- throw new Error(`Trying to join ${field}, but ${fromField} is not a defined relation on ${meta.className}`);
793
+ throw new Error(`Trying to join ${q(field)}, but ${q(fromField)} is not a defined relation on ${meta.className}.`);
572
794
  }
573
795
  this.createAlias(prop.type, alias);
574
796
  cond = core_1.QueryHelper.processWhere({
@@ -582,24 +804,24 @@ class QueryBuilder {
582
804
  let aliasedName = `${fromAlias}.${prop.name}#${alias}`;
583
805
  path ??= `${(Object.values(this._joins).find(j => j.alias === fromAlias)?.path ?? entityName)}.${prop.name}`;
584
806
  if (prop.kind === core_1.ReferenceKind.ONE_TO_MANY) {
585
- this._joins[aliasedName] = this.helper.joinOneToReference(prop, fromAlias, alias, type, cond);
807
+ this._joins[aliasedName] = this.helper.joinOneToReference(prop, fromAlias, alias, type, cond, schema);
586
808
  }
587
809
  else if (prop.kind === core_1.ReferenceKind.MANY_TO_MANY) {
588
810
  let pivotAlias = alias;
589
- if (type !== 'pivotJoin') {
811
+ if (type !== enums_1.JoinType.pivotJoin) {
590
812
  const oldPivotAlias = this.getAliasForJoinPath(path + '[pivot]');
591
813
  pivotAlias = oldPivotAlias ?? this.getNextAlias(prop.pivotEntity);
592
814
  aliasedName = `${fromAlias}.${prop.name}#${pivotAlias}`;
593
815
  }
594
- const joins = this.helper.joinManyToManyReference(prop, fromAlias, alias, pivotAlias, type, cond, path);
816
+ const joins = this.helper.joinManyToManyReference(prop, fromAlias, alias, pivotAlias, type, cond, path, schema);
595
817
  Object.assign(this._joins, joins);
596
818
  this.createAlias(prop.pivotEntity, pivotAlias);
597
819
  }
598
820
  else if (prop.kind === core_1.ReferenceKind.ONE_TO_ONE) {
599
- this._joins[aliasedName] = this.helper.joinOneToReference(prop, fromAlias, alias, type, cond);
821
+ this._joins[aliasedName] = this.helper.joinOneToReference(prop, fromAlias, alias, type, cond, schema);
600
822
  }
601
823
  else { // MANY_TO_ONE
602
- this._joins[aliasedName] = this.helper.joinManyToOneReference(prop, fromAlias, alias, type, cond);
824
+ this._joins[aliasedName] = this.helper.joinManyToOneReference(prop, fromAlias, alias, type, cond, schema);
603
825
  }
604
826
  if (!this._joins[aliasedName].path && path) {
605
827
  this._joins[aliasedName].path = path;
@@ -608,13 +830,27 @@ class QueryBuilder {
608
830
  }
609
831
  prepareFields(fields, type = 'where') {
610
832
  const ret = [];
833
+ const getFieldName = (name) => {
834
+ if (type === 'groupBy') {
835
+ return this.helper.mapper(name, this.type, undefined, null);
836
+ }
837
+ return this.helper.mapper(name, this.type);
838
+ };
611
839
  fields.forEach(field => {
840
+ const rawField = core_1.RawQueryFragment.getKnownFragment(field);
841
+ if (rawField) {
842
+ const sql = this.platform.formatQuery(rawField.sql, rawField.params);
843
+ ret.push(this.knex.raw(sql));
844
+ return;
845
+ }
612
846
  if (!core_1.Utils.isString(field)) {
613
- return ret.push(field);
847
+ ret.push(field);
848
+ return;
614
849
  }
615
850
  const join = Object.keys(this._joins).find(k => field === k.substring(0, k.indexOf('#')));
616
851
  if (join && type === 'where') {
617
- return ret.push(...this.helper.mapJoinColumns(this.type ?? enums_1.QueryType.SELECT, this._joins[join]));
852
+ ret.push(...this.helper.mapJoinColumns(this.type ?? enums_1.QueryType.SELECT, this._joins[join]));
853
+ return;
618
854
  }
619
855
  const [a, f] = this.helper.splitField(field);
620
856
  const prop = this.helper.getProperty(f, a);
@@ -623,19 +859,20 @@ class QueryBuilder {
623
859
  return;
624
860
  }
625
861
  if (prop?.embedded) {
626
- const fieldName = this.helper.mapper(prop.fieldNames[0], this.type);
627
- ret.push(fieldName);
862
+ const name = prop.embeddedPath?.join('.') ?? prop.fieldNames[0];
863
+ const aliased = this._aliases[a] ? `${a}.${name}` : name;
864
+ ret.push(getFieldName(aliased));
628
865
  return;
629
866
  }
630
867
  if (prop?.kind === core_1.ReferenceKind.EMBEDDED) {
631
868
  if (prop.object) {
632
- ret.push(this.helper.mapper(prop.fieldNames[0], this.type));
869
+ ret.push(getFieldName(prop.fieldNames[0]));
633
870
  }
634
871
  else {
635
872
  const nest = (prop) => {
636
873
  for (const childProp of Object.values(prop.embeddedProps)) {
637
- if (childProp.fieldNames) {
638
- ret.push(this.helper.mapper(childProp.fieldNames[0], this.type));
874
+ if (childProp.fieldNames && (childProp.kind !== core_1.ReferenceKind.EMBEDDED || childProp.object) && childProp.persist !== false) {
875
+ ret.push(getFieldName(childProp.fieldNames[0]));
639
876
  }
640
877
  else {
641
878
  nest(childProp);
@@ -646,11 +883,15 @@ class QueryBuilder {
646
883
  }
647
884
  return;
648
885
  }
649
- ret.push(this.helper.mapper(field, this.type));
886
+ if (prop && prop.fieldNames.length > 1) {
887
+ ret.push(...prop.fieldNames.map(f => getFieldName(f)));
888
+ return;
889
+ }
890
+ ret.push(getFieldName(field));
650
891
  });
651
892
  const meta = this.mainAlias.metadata;
652
893
  /* istanbul ignore next */
653
- const requiresSQLConversion = meta?.props.filter(p => p.customType?.convertToJSValueSQL) ?? [];
894
+ const requiresSQLConversion = meta?.props.filter(p => p.hasConvertToJSValueSQL && p.persist !== false) ?? [];
654
895
  if (this.flags.has(core_1.QueryFlag.CONVERT_CUSTOM_TYPES) && (fields.includes('*') || fields.includes(`${this.mainAlias.aliasName}.*`)) && requiresSQLConversion.length > 0) {
655
896
  requiresSQLConversion.forEach(p => ret.push(this.helper.mapper(p.name, this.type)));
656
897
  }
@@ -659,11 +900,6 @@ class QueryBuilder {
659
900
  const cols = this.helper.mapJoinColumns(this.type ?? enums_1.QueryType.SELECT, this._joins[f]);
660
901
  ret.push(...cols);
661
902
  }
662
- if (this._joins[f].prop.kind !== core_1.ReferenceKind.ONE_TO_ONE && this._joins[f].inverseJoinColumns) {
663
- this._joins[f].inverseJoinColumns.forEach(inverseJoinColumn => {
664
- core_1.Utils.renameKey(this._cond, inverseJoinColumn, `${this._joins[f].alias}.${inverseJoinColumn}`);
665
- });
666
- }
667
903
  });
668
904
  return ret;
669
905
  }
@@ -678,10 +914,9 @@ class QueryBuilder {
678
914
  }
679
915
  if (data) {
680
916
  if (core_1.Utils.isEntity(data)) {
681
- // data = helper(data).toJSON();
682
917
  data = this.em?.getComparator().prepareEntity(data) ?? (0, core_1.serialize)(data);
683
918
  }
684
- this._data = this.helper.processData(data, this.flags.has(core_1.QueryFlag.CONVERT_CUSTOM_TYPES));
919
+ this._data = this.helper.processData(data, this.flags.has(core_1.QueryFlag.CONVERT_CUSTOM_TYPES), false);
685
920
  }
686
921
  if (cond) {
687
922
  this.where(cond);
@@ -691,6 +926,8 @@ class QueryBuilder {
691
926
  getQueryBase() {
692
927
  const qb = this.getKnex();
693
928
  const schema = this.getSchema(this.mainAlias);
929
+ // Joined tables doesn't need to belong to the same schema as the main table
930
+ const joinSchema = this._schema ?? this.em?.schema ?? schema;
694
931
  if (schema) {
695
932
  qb.withSchema(schema);
696
933
  }
@@ -709,12 +946,12 @@ class QueryBuilder {
709
946
  else if (this.flags.has(core_1.QueryFlag.DISTINCT)) {
710
947
  qb.distinct();
711
948
  }
712
- this.helper.processJoins(qb, this._joins, schema);
949
+ this.helper.processJoins(qb, this._joins, joinSchema);
713
950
  break;
714
951
  case enums_1.QueryType.COUNT: {
715
952
  const m = this.flags.has(core_1.QueryFlag.DISTINCT) ? 'countDistinct' : 'count';
716
953
  qb[m]({ count: this._fields.map(f => this.helper.mapper(f, this.type)) });
717
- this.helper.processJoins(qb, this._joins, schema);
954
+ this.helper.processJoins(qb, this._joins, joinSchema);
718
955
  break;
719
956
  }
720
957
  case enums_1.QueryType.INSERT:
@@ -733,6 +970,24 @@ class QueryBuilder {
733
970
  }
734
971
  return qb;
735
972
  }
973
+ applyDiscriminatorCondition() {
974
+ const meta = this.mainAlias.metadata;
975
+ if (!meta?.discriminatorValue) {
976
+ return;
977
+ }
978
+ const types = Object.values(meta.root.discriminatorMap).map(cls => this.metadata.find(cls));
979
+ const children = [];
980
+ const lookUpChildren = (ret, type) => {
981
+ const children = types.filter(meta2 => meta2.extends === type);
982
+ children.forEach(m => lookUpChildren(ret, m.className));
983
+ ret.push(...children.filter(c => c.discriminatorValue));
984
+ return children;
985
+ };
986
+ lookUpChildren(children, meta.className);
987
+ this.andWhere({
988
+ [meta.root.discriminatorColumn]: children.length > 0 ? { $in: [meta.discriminatorValue, ...children.map(c => c.discriminatorValue)] } : meta.discriminatorValue,
989
+ });
990
+ }
736
991
  finalize() {
737
992
  if (this.finalized) {
738
993
  return;
@@ -741,11 +996,12 @@ class QueryBuilder {
741
996
  this.select('*');
742
997
  }
743
998
  const meta = this.mainAlias.metadata;
999
+ this.applyDiscriminatorCondition();
744
1000
  if (meta && this.flags.has(core_1.QueryFlag.AUTO_JOIN_ONE_TO_ONE_OWNER)) {
745
1001
  const relationsToPopulate = this._populate.map(({ field }) => field);
746
1002
  meta.relations
747
- .filter(prop => prop.kind === core_1.ReferenceKind.ONE_TO_ONE && !prop.owner && !relationsToPopulate.includes(prop.name))
748
- .map(prop => ({ field: prop.name }))
1003
+ .filter(prop => prop.kind === core_1.ReferenceKind.ONE_TO_ONE && !prop.owner && !relationsToPopulate.includes(prop.name) && !relationsToPopulate.includes(`${prop.name}:ref`))
1004
+ .map(prop => ({ field: `${prop.name}:ref` }))
749
1005
  .forEach(item => this._populate.push(item));
750
1006
  }
751
1007
  this._populate.forEach(({ field }) => {
@@ -753,16 +1009,14 @@ class QueryBuilder {
753
1009
  const aliasedField = `${fromAlias}.${fromField}`;
754
1010
  const join = Object.keys(this._joins).find(k => `${aliasedField}#${this._joins[k].alias}` === k);
755
1011
  if (join && this._joins[join] && this.helper.isOneToOneInverse(fromField)) {
756
- return this._populateMap[join] = this._joins[join].alias;
757
- }
758
- if (this.metadata.find(field)?.pivotTable) { // pivot table entity
759
- this.autoJoinPivotTable(field);
1012
+ this._populateMap[join] = this._joins[join].alias;
1013
+ return;
760
1014
  }
761
- else if (meta && this.helper.isOneToOneInverse(fromField)) {
1015
+ if (meta && this.helper.isOneToOneInverse(fromField)) {
762
1016
  const prop = meta.properties[fromField];
763
1017
  const alias = this.getNextAlias(prop.pivotEntity ?? prop.type);
764
1018
  const aliasedName = `${fromAlias}.${prop.name}#${alias}`;
765
- this._joins[aliasedName] = this.helper.joinOneToReference(prop, this.mainAlias.aliasName, alias, 'leftJoin');
1019
+ this._joins[aliasedName] = this.helper.joinOneToReference(prop, this.mainAlias.aliasName, alias, enums_1.JoinType.leftJoin);
766
1020
  this._populateMap[aliasedName] = this._joins[aliasedName].alias;
767
1021
  }
768
1022
  });
@@ -774,14 +1028,20 @@ class QueryBuilder {
774
1028
  const aliased = this.knex.ref(prop.fieldNames[0]).toString();
775
1029
  return `${prop.formula(alias)} as ${aliased}`;
776
1030
  })
777
- .filter(field => !this._fields.includes(field))
778
- .forEach(field => this.addSelect(field));
1031
+ .filter(field => !this._fields.some(f => {
1032
+ if (f instanceof core_1.RawQueryFragment) {
1033
+ return f.sql === field && f.params.length === 0;
1034
+ }
1035
+ return f === field;
1036
+ }))
1037
+ .forEach(field => this._fields.push((0, core_1.raw)(field)));
779
1038
  }
1039
+ this.processPopulateWhere();
780
1040
  core_1.QueryHelper.processObjectParams(this._data);
781
1041
  core_1.QueryHelper.processObjectParams(this._cond);
782
1042
  core_1.QueryHelper.processObjectParams(this._having);
783
- // automatically enable paginate flag when we detect to-many joins
784
- if (!this.flags.has(core_1.QueryFlag.DISABLE_PAGINATE) && this.hasToManyJoins()) {
1043
+ // automatically enable paginate flag when we detect to-many joins, but only if there is no `group by` clause
1044
+ if (!this.flags.has(core_1.QueryFlag.DISABLE_PAGINATE) && this._groupBy.length === 0 && this.hasToManyJoins()) {
785
1045
  this.flags.add(core_1.QueryFlag.PAGINATE);
786
1046
  }
787
1047
  if (meta && this.flags.has(core_1.QueryFlag.PAGINATE) && (this._limit > 0 || this._offset > 0)) {
@@ -792,6 +1052,43 @@ class QueryBuilder {
792
1052
  }
793
1053
  this.finalized = true;
794
1054
  }
1055
+ processPopulateWhere() {
1056
+ if (this._populateWhere == null || this._populateWhere === core_1.PopulateHint.ALL) {
1057
+ return;
1058
+ }
1059
+ const joins = Object.values(this._joins);
1060
+ joins.forEach(join => {
1061
+ join.cond_ = join.cond;
1062
+ join.cond = {};
1063
+ });
1064
+ const replaceOnConditions = (cond, op) => {
1065
+ Object.keys(cond).forEach(k => {
1066
+ if (core_1.Utils.isOperator(k)) {
1067
+ if (Array.isArray(cond[k])) {
1068
+ return cond[k].forEach((c) => replaceOnConditions(c, k));
1069
+ }
1070
+ /* istanbul ignore next */
1071
+ return replaceOnConditions(cond[k], k);
1072
+ }
1073
+ const [alias] = this.helper.splitField(k);
1074
+ const join = joins.find(j => j.alias === alias);
1075
+ if (join) {
1076
+ if (join.cond[k]) {
1077
+ join.cond = { [op ?? '$and']: [join.cond, { [k]: cond[k] }] };
1078
+ }
1079
+ else {
1080
+ join.cond = { ...join.cond, [k]: cond[k] };
1081
+ }
1082
+ }
1083
+ });
1084
+ };
1085
+ if (typeof this._populateWhere === 'object') {
1086
+ const cond = CriteriaNodeFactory_1.CriteriaNodeFactory
1087
+ .createNode(this.metadata, this.mainAlias.entityName, this._populateWhere)
1088
+ .process(this, { matchPopulateJoins: true, ignoreBranching: true, preferNoBranch: true });
1089
+ replaceOnConditions(cond);
1090
+ }
1091
+ }
795
1092
  hasToManyJoins() {
796
1093
  return Object.values(this._joins).some(join => {
797
1094
  return [core_1.ReferenceKind.ONE_TO_MANY, core_1.ReferenceKind.MANY_TO_MANY].includes(join.prop.kind);
@@ -800,9 +1097,12 @@ class QueryBuilder {
800
1097
  wrapPaginateSubQuery(meta) {
801
1098
  const pks = this.prepareFields(meta.primaryKeys, 'sub-query');
802
1099
  const subQuery = this.clone().select(pks).groupBy(pks).limit(this._limit);
1100
+ // revert the on conditions added via populateWhere, we want to apply those only once
1101
+ Object.values(subQuery._joins).forEach(join => join.cond = join.cond_ ?? {});
803
1102
  if (this._offset) {
804
1103
  subQuery.offset(this._offset);
805
1104
  }
1105
+ const addToSelect = [];
806
1106
  if (this._orderBy.length > 0) {
807
1107
  const orderBy = [];
808
1108
  for (const orderMap of this._orderBy) {
@@ -810,28 +1110,38 @@ class QueryBuilder {
810
1110
  const [a, f] = this.helper.splitField(field);
811
1111
  const prop = this.helper.getProperty(f, a);
812
1112
  const type = this.platform.castColumn(prop);
813
- orderBy.push({
814
- [`min(${this.ref(this.helper.mapper(field, this.type, undefined, null))}${type})`]: direction,
815
- });
1113
+ const fieldName = this.helper.mapper(field, this.type, undefined, null);
1114
+ if (!prop?.persist && !prop?.formula) {
1115
+ addToSelect.push(fieldName);
1116
+ }
1117
+ orderBy.push({ [(0, core_1.raw)(`min(${this.knex.ref(fieldName)}${type})`)]: direction });
816
1118
  }
817
1119
  }
818
1120
  subQuery.orderBy(orderBy);
819
1121
  }
820
1122
  subQuery.finalized = true;
821
1123
  const knexQuery = subQuery.as(this.mainAlias.aliasName).clearSelect().select(pks);
1124
+ if (addToSelect.length > 0) {
1125
+ addToSelect.forEach(prop => {
1126
+ const field = this._fields.find(field => {
1127
+ if (typeof field === 'object' && field && '__as' in field) {
1128
+ return field.__as === prop;
1129
+ }
1130
+ // not perfect, but should work most of the time, ideally we should check only the alias (`... as alias`)
1131
+ return field.toString().includes(prop);
1132
+ });
1133
+ if (field) {
1134
+ knexQuery.select(field);
1135
+ }
1136
+ });
1137
+ }
822
1138
  // multiple sub-queries are needed to get around mysql limitations with order by + limit + where in + group by (o.O)
823
1139
  // https://stackoverflow.com/questions/17892762/mysql-this-version-of-mysql-doesnt-yet-support-limit-in-all-any-some-subqu
824
1140
  const subSubQuery = this.getKnex().select(pks).from(knexQuery);
1141
+ subSubQuery.__raw = true; // tag it as there is now way to check via `instanceof`
825
1142
  this._limit = undefined;
826
1143
  this._offset = undefined;
827
- const cond = this._cond;
828
1144
  this.select(this._fields).where({ [core_1.Utils.getPrimaryKeyHash(meta.primaryKeys)]: { $in: subSubQuery } });
829
- if (this._populateWhere === core_1.PopulateHint.INFER) {
830
- this.andWhere(cond);
831
- }
832
- else if (typeof this._populateWhere === 'object') {
833
- this.andWhere(this._populateWhere);
834
- }
835
1145
  }
836
1146
  wrapModifySubQuery(meta) {
837
1147
  const subQuery = this.clone();
@@ -845,21 +1155,10 @@ class QueryBuilder {
845
1155
  [core_1.Utils.getPrimaryKeyHash(meta.primaryKeys)]: { $in: subSubQuery },
846
1156
  });
847
1157
  }
848
- autoJoinPivotTable(field) {
849
- const pivotMeta = this.metadata.find(field);
850
- const owner = pivotMeta.relations[0];
851
- const inverse = pivotMeta.relations[1];
852
- const prop = this._cond[pivotMeta.name + '.' + owner.name] || this._orderBy[pivotMeta.name + '.' + owner.name] ? inverse : owner;
853
- const pivotAlias = this.getNextAlias(pivotMeta.name);
854
- this._joins[field] = this.helper.joinPivotTable(field, prop, this.mainAlias.aliasName, pivotAlias, 'leftJoin');
855
- core_1.Utils.renameKey(this._cond, `${field}.${owner.name}`, core_1.Utils.getPrimaryKeyHash(owner.fieldNames.map(fieldName => `${pivotAlias}.${fieldName}`)));
856
- core_1.Utils.renameKey(this._cond, `${field}.${inverse.name}`, core_1.Utils.getPrimaryKeyHash(inverse.fieldNames.map(fieldName => `${pivotAlias}.${fieldName}`)));
857
- this._populateMap[field] = this._joins[field].alias;
858
- }
859
1158
  getSchema(alias) {
860
1159
  const { metadata } = alias;
861
1160
  const metaSchema = metadata?.schema && metadata.schema !== '*' ? metadata.schema : undefined;
862
- return this._schema ?? metaSchema ?? this.em?.config.get('schema');
1161
+ return this._schema ?? metaSchema ?? this.em?.schema ?? this.em?.config.get('schema');
863
1162
  }
864
1163
  createAlias(entityName, aliasName, subQuery) {
865
1164
  const metadata = this.metadata.find(entityName);
@@ -897,6 +1196,7 @@ class QueryBuilder {
897
1196
  }
898
1197
  }
899
1198
  /* istanbul ignore next */
1199
+ /** @ignore */
900
1200
  [util_1.inspect.custom](depth) {
901
1201
  const object = { ...this };
902
1202
  const hidden = ['metadata', 'driver', 'context', 'platform', 'knex', 'type'];
@@ -907,6 +1207,9 @@ class QueryBuilder {
907
1207
  if (this._data) {
908
1208
  object.data = this._data;
909
1209
  }
1210
+ if (this._schema) {
1211
+ object.schema = this._schema;
1212
+ }
910
1213
  if (!core_1.Utils.isEmpty(this._cond)) {
911
1214
  object.where = this._cond;
912
1215
  }