@mikro-orm/knex 6.0.0-dev.10 → 6.0.0-dev.101

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 (39) hide show
  1. package/AbstractSqlConnection.d.ts +8 -4
  2. package/AbstractSqlConnection.js +30 -18
  3. package/AbstractSqlDriver.d.ts +22 -22
  4. package/AbstractSqlDriver.js +233 -88
  5. package/AbstractSqlPlatform.d.ts +5 -4
  6. package/AbstractSqlPlatform.js +34 -5
  7. package/MonkeyPatchable.d.ts +1 -0
  8. package/MonkeyPatchable.js +3 -0
  9. package/README.md +64 -107
  10. package/SqlEntityManager.d.ts +2 -6
  11. package/SqlEntityManager.js +5 -9
  12. package/SqlEntityRepository.d.ts +7 -6
  13. package/SqlEntityRepository.js +11 -8
  14. package/index.mjs +22 -6
  15. package/package.json +8 -8
  16. package/query/ArrayCriteriaNode.d.ts +3 -3
  17. package/query/CriteriaNode.d.ts +8 -8
  18. package/query/CriteriaNode.js +16 -9
  19. package/query/CriteriaNodeFactory.d.ts +6 -6
  20. package/query/CriteriaNodeFactory.js +10 -13
  21. package/query/ObjectCriteriaNode.d.ts +3 -3
  22. package/query/ObjectCriteriaNode.js +12 -8
  23. package/query/QueryBuilder.d.ts +41 -19
  24. package/query/QueryBuilder.js +319 -110
  25. package/query/QueryBuilderHelper.d.ts +9 -13
  26. package/query/QueryBuilderHelper.js +123 -97
  27. package/query/ScalarCriteriaNode.d.ts +3 -2
  28. package/query/ScalarCriteriaNode.js +9 -6
  29. package/query/enums.js +1 -1
  30. package/schema/DatabaseSchema.d.ts +5 -2
  31. package/schema/DatabaseSchema.js +26 -8
  32. package/schema/DatabaseTable.d.ts +4 -2
  33. package/schema/DatabaseTable.js +53 -28
  34. package/schema/SchemaComparator.js +12 -2
  35. package/schema/SchemaHelper.d.ts +6 -4
  36. package/schema/SchemaHelper.js +11 -4
  37. package/schema/SqlSchemaGenerator.d.ts +3 -6
  38. package/schema/SqlSchemaGenerator.js +61 -28
  39. package/typings.d.ts +12 -7
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.QueryBuilder = void 0;
4
+ const util_1 = require("util");
4
5
  const core_1 = require("@mikro-orm/core");
5
6
  const enums_1 = require("./enums");
6
7
  const QueryBuilderHelper_1 = require("./QueryBuilderHelper");
@@ -25,6 +26,12 @@ const CriteriaNodeFactory_1 = require("./CriteriaNodeFactory");
25
26
  * ```
26
27
  */
27
28
  class QueryBuilder {
29
+ metadata;
30
+ driver;
31
+ context;
32
+ connectionType;
33
+ em;
34
+ logging;
28
35
  get mainAlias() {
29
36
  this.ensureFromClause();
30
37
  return this._mainAlias;
@@ -36,31 +43,56 @@ class QueryBuilder {
36
43
  this.ensureFromClause();
37
44
  return this._helper;
38
45
  }
46
+ /** @internal */
47
+ type;
48
+ /** @internal */
49
+ _fields;
50
+ /** @internal */
51
+ _populate = [];
52
+ /** @internal */
53
+ _populateWhere;
54
+ /** @internal */
55
+ _populateMap = {};
56
+ aliasCounter = 0;
57
+ flags = new Set([core_1.QueryFlag.CONVERT_CUSTOM_TYPES]);
58
+ finalized = false;
59
+ _joins = {};
60
+ _explicitAlias = false;
61
+ _schema;
62
+ _cond = {};
63
+ _data;
64
+ _orderBy = [];
65
+ _groupBy = [];
66
+ _having = {};
67
+ _returning;
68
+ _onConflict;
69
+ _limit;
70
+ _offset;
71
+ _distinctOn;
72
+ _joinedProps = new Map();
73
+ _cache;
74
+ _indexHint;
75
+ _comments = [];
76
+ _hintComments = [];
77
+ flushMode;
78
+ lockMode;
79
+ lockTables;
80
+ subQueries = {};
81
+ _mainAlias;
82
+ _aliases = {};
83
+ _helper;
84
+ platform;
85
+ knex;
39
86
  /**
40
87
  * @internal
41
88
  */
42
- constructor(entityName, metadata, driver, context, alias, connectionType, em) {
89
+ constructor(entityName, metadata, driver, context, alias, connectionType, em, logging) {
43
90
  this.metadata = metadata;
44
91
  this.driver = driver;
45
92
  this.context = context;
46
93
  this.connectionType = connectionType;
47
94
  this.em = em;
48
- /** @internal */
49
- this._populate = [];
50
- /** @internal */
51
- this._populateMap = {};
52
- this.aliasCounter = 0;
53
- this.flags = new Set([core_1.QueryFlag.CONVERT_CUSTOM_TYPES]);
54
- this.finalized = false;
55
- this._joins = {};
56
- this._explicitAlias = false;
57
- this._cond = {};
58
- this._orderBy = [];
59
- this._groupBy = [];
60
- this._having = {};
61
- this._joinedProps = new Map();
62
- this.subQueries = {};
63
- this._aliases = {};
95
+ this.logging = logging;
64
96
  this.platform = this.driver.getPlatform();
65
97
  this.knex = this.driver.getConnection(this.connectionType).getKnex();
66
98
  if (alias) {
@@ -115,26 +147,26 @@ class QueryBuilder {
115
147
  this._fields = this.mainAlias.metadata.primaryKeys;
116
148
  }
117
149
  else {
118
- this._fields = [this.raw('*')];
150
+ this._fields = [(0, core_1.raw)('*')];
119
151
  }
120
152
  if (distinct) {
121
153
  this.flags.add(core_1.QueryFlag.DISTINCT);
122
154
  }
123
155
  return this.init(enums_1.QueryType.COUNT);
124
156
  }
125
- join(field, alias, cond = {}, type = 'innerJoin', path) {
126
- this.joinReference(field, alias, cond, type, path);
157
+ join(field, alias, cond = {}, type = 'innerJoin', path, schema) {
158
+ this.joinReference(field, alias, cond, type, path, schema);
127
159
  return this;
128
160
  }
129
- leftJoin(field, alias, cond = {}) {
130
- return this.join(field, alias, cond, 'leftJoin');
161
+ leftJoin(field, alias, cond = {}, schema) {
162
+ return this.join(field, alias, cond, 'leftJoin', undefined, schema);
131
163
  }
132
- joinAndSelect(field, alias, cond = {}, type = 'innerJoin', path) {
164
+ joinAndSelect(field, alias, cond = {}, type = 'innerJoin', path, fields, schema) {
133
165
  if (!this.type) {
134
166
  this.select('*');
135
167
  }
136
- const prop = this.joinReference(field, alias, cond, type, path);
137
- this.addSelect(this.getFieldsForJoinedLoad(prop, alias));
168
+ const prop = this.joinReference(field, alias, cond, type, path, schema);
169
+ this.addSelect(this.getFieldsForJoinedLoad(prop, alias, fields));
138
170
  const [fromAlias] = this.helper.splitField(field);
139
171
  const populate = this._joinedProps.get(fromAlias);
140
172
  const item = { field: prop.name, strategy: core_1.LoadStrategy.JOINED, children: [] };
@@ -147,13 +179,16 @@ class QueryBuilder {
147
179
  this._joinedProps.set(alias, item);
148
180
  return this;
149
181
  }
150
- leftJoinAndSelect(field, alias, cond = {}) {
151
- return this.joinAndSelect(field, alias, cond, 'leftJoin');
182
+ leftJoinAndSelect(field, alias, cond = {}, fields, schema) {
183
+ return this.joinAndSelect(field, alias, cond, 'leftJoin', undefined, fields, schema);
184
+ }
185
+ innerJoinAndSelect(field, alias, cond = {}, fields, schema) {
186
+ return this.joinAndSelect(field, alias, cond, 'innerJoin', undefined, fields, schema);
152
187
  }
153
- getFieldsForJoinedLoad(prop, alias) {
188
+ getFieldsForJoinedLoad(prop, alias, explicitFields) {
154
189
  const fields = [];
155
190
  prop.targetMeta.props
156
- .filter(prop => this.platform.shouldHaveColumn(prop, this._populate))
191
+ .filter(prop => explicitFields ? explicitFields.includes(prop.name) || prop.primary : this.platform.shouldHaveColumn(prop, this._populate))
157
192
  .forEach(prop => fields.push(...this.driver.mapPropToFieldNames(this, prop, alias)));
158
193
  return fields;
159
194
  }
@@ -164,9 +199,15 @@ class QueryBuilder {
164
199
  }
165
200
  where(cond, params, operator) {
166
201
  this.ensureNotFinalized();
167
- if (core_1.Utils.isString(cond)) {
168
- cond = { [`(${cond})`]: core_1.Utils.asArray(params) };
169
- operator = operator || '$and';
202
+ const rawField = core_1.RawQueryFragment.getKnownFragment(cond);
203
+ if (rawField) {
204
+ const sql = this.platform.formatQuery(rawField.sql, rawField.params);
205
+ cond = { [(0, core_1.raw)(`(${sql})`)]: core_1.Utils.asArray(params) };
206
+ operator ??= '$and';
207
+ }
208
+ else if (core_1.Utils.isString(cond)) {
209
+ cond = { [(0, core_1.raw)(`(${cond})`, core_1.Utils.asArray(params))]: [] };
210
+ operator ??= '$and';
170
211
  }
171
212
  else {
172
213
  cond = core_1.QueryHelper.processWhere({
@@ -234,15 +275,22 @@ class QueryBuilder {
234
275
  having(cond = {}, params) {
235
276
  this.ensureNotFinalized();
236
277
  if (core_1.Utils.isString(cond)) {
237
- cond = { [`(${cond})`]: core_1.Utils.asArray(params) };
278
+ cond = { [(0, core_1.raw)(`(${cond})`, params)]: [] };
238
279
  }
239
280
  this._having = CriteriaNodeFactory_1.CriteriaNodeFactory.createNode(this.metadata, this.mainAlias.entityName, cond).process(this);
240
281
  return this;
241
282
  }
242
283
  onConflict(fields = []) {
284
+ const meta = this.mainAlias.metadata;
243
285
  this.ensureNotFinalized();
244
- this._onConflict = this._onConflict || [];
245
- this._onConflict.push({ fields: core_1.Utils.asArray(fields).map(f => f.toString()) });
286
+ this._onConflict ??= [];
287
+ this._onConflict.push({
288
+ fields: core_1.Utils.asArray(fields).flatMap(f => {
289
+ const key = f.toString();
290
+ /* istanbul ignore next */
291
+ return meta.properties[key]?.fieldNames ?? [key];
292
+ }),
293
+ });
246
294
  return this;
247
295
  }
248
296
  ignore() {
@@ -262,6 +310,10 @@ class QueryBuilder {
262
310
  this._onConflict[this._onConflict.length - 1].merge = data;
263
311
  return this;
264
312
  }
313
+ returning(fields) {
314
+ this._returning = fields != null ? core_1.Utils.asArray(fields) : fields;
315
+ return this;
316
+ }
265
317
  /**
266
318
  * @internal
267
319
  */
@@ -271,17 +323,6 @@ class QueryBuilder {
271
323
  this._populateWhere = populateWhere;
272
324
  return this;
273
325
  }
274
- /**
275
- * @internal
276
- */
277
- ref(field) {
278
- return this.knex.ref(field);
279
- }
280
- raw(sql, bindings = []) {
281
- const raw = this.knex.raw(sql, bindings);
282
- raw.__raw = true; // tag it as there is now way to check via `instanceof`
283
- return raw;
284
- }
285
326
  limit(limit, offset = 0) {
286
327
  this.ensureNotFinalized();
287
328
  this._limit = limit;
@@ -337,6 +378,24 @@ class QueryBuilder {
337
378
  this._indexHint = sql;
338
379
  return this;
339
380
  }
381
+ /**
382
+ * Prepend comment to the sql query using the syntax `/* ... *‍/`. Some characters are forbidden such as `/*, *‍/` and `?`.
383
+ */
384
+ comment(comment) {
385
+ this.ensureNotFinalized();
386
+ this._comments.push(...core_1.Utils.asArray(comment));
387
+ return this;
388
+ }
389
+ /**
390
+ * Add hints to the query using comment-like syntax `/*+ ... *‍/`. MySQL and Oracle use this syntax for optimizer hints.
391
+ * Also various DB proxies and routers use this syntax to pass hints to alter their behavior. In other dialects the hints
392
+ * are ignored as simple comments.
393
+ */
394
+ hintComment(comment) {
395
+ this.ensureNotFinalized();
396
+ this._hintComments.push(...core_1.Utils.asArray(comment));
397
+ return this;
398
+ }
340
399
  from(target, aliasName) {
341
400
  this.ensureNotFinalized();
342
401
  if (target instanceof QueryBuilder) {
@@ -354,17 +413,21 @@ class QueryBuilder {
354
413
  getKnexQuery() {
355
414
  this.finalize();
356
415
  const qb = this.getQueryBase();
416
+ qb.__raw = true; // tag it as there is now way to check via `instanceof`
357
417
  core_1.Utils.runIfNotEmpty(() => this.helper.appendQueryCondition(this.type ?? enums_1.QueryType.SELECT, this._cond, qb), this._cond && !this._onConflict);
358
418
  core_1.Utils.runIfNotEmpty(() => qb.groupBy(this.prepareFields(this._groupBy, 'groupBy')), this._groupBy);
359
419
  core_1.Utils.runIfNotEmpty(() => this.helper.appendQueryCondition(this.type ?? enums_1.QueryType.SELECT, this._having, qb, undefined, 'having'), this._having);
360
420
  core_1.Utils.runIfNotEmpty(() => {
361
421
  const queryOrder = this.helper.getQueryOrder(this.type ?? enums_1.QueryType.SELECT, this._orderBy, this._populateMap);
362
422
  if (queryOrder) {
363
- return qb.orderByRaw(queryOrder);
423
+ qb.orderByRaw(queryOrder);
424
+ return;
364
425
  }
365
426
  }, this._orderBy);
366
427
  core_1.Utils.runIfNotEmpty(() => qb.limit(this._limit), this._limit != null);
367
428
  core_1.Utils.runIfNotEmpty(() => qb.offset(this._offset), this._offset);
429
+ core_1.Utils.runIfNotEmpty(() => this._comments.forEach(comment => qb.comment(comment)), this._comments);
430
+ core_1.Utils.runIfNotEmpty(() => this._hintComments.forEach(comment => qb.hintComment(comment)), this._hintComments);
368
431
  core_1.Utils.runIfNotEmpty(() => this.helper.appendOnConflictClause(this.type ?? enums_1.QueryType.SELECT, this._onConflict, qb), this._onConflict);
369
432
  if (this.type === enums_1.QueryType.TRUNCATE && this.platform.usesCascadeStatement()) {
370
433
  return this.knex.raw(qb.toSQL().toNative().sql + ' cascade');
@@ -372,26 +435,35 @@ class QueryBuilder {
372
435
  if (this.lockMode) {
373
436
  this.helper.getLockSQL(qb, this.lockMode, this.lockTables);
374
437
  }
375
- this.helper.finalize(this.type ?? enums_1.QueryType.SELECT, qb, this.mainAlias.metadata);
438
+ this.helper.finalize(this.type ?? enums_1.QueryType.SELECT, qb, this.mainAlias.metadata, this._data, this._returning);
376
439
  return qb;
377
440
  }
378
441
  /**
379
442
  * Returns the query with parameters as wildcards.
380
443
  */
381
444
  getQuery() {
382
- return this.getKnexQuery().toSQL().toNative().sql;
445
+ return this.toQuery().sql;
446
+ }
447
+ #query;
448
+ toQuery() {
449
+ if (this.#query) {
450
+ return this.#query;
451
+ }
452
+ const sql = this.getKnexQuery().toSQL();
453
+ const query = sql.toNative();
454
+ return this.#query = { sql: query.sql, params: query.bindings ?? [], _sql: sql };
383
455
  }
384
456
  /**
385
457
  * Returns the list of all parameters for this query.
386
458
  */
387
459
  getParams() {
388
- return this.getKnexQuery().toSQL().toNative().bindings;
460
+ return this.toQuery().params;
389
461
  }
390
462
  /**
391
463
  * Returns raw interpolated query string with all the parameters inlined.
392
464
  */
393
465
  getFormattedQuery() {
394
- const query = this.getKnexQuery().toSQL();
466
+ const query = this.toQuery()._sql;
395
467
  return this.platform.formatQuery(query.sql, query.bindings);
396
468
  }
397
469
  /**
@@ -433,7 +505,7 @@ class QueryBuilder {
433
505
  return cached.data;
434
506
  }
435
507
  const type = this.connectionType || (method === 'run' ? 'write' : 'read');
436
- const res = await this.driver.getConnection(type).execute(query.sql, query.bindings, method, this.context);
508
+ const res = await this.driver.getConnection(type).execute(query.sql, query.bindings, method, this.context, this.logging);
437
509
  const meta = this.mainAlias.metadata;
438
510
  if (!mapResults || !meta) {
439
511
  await this.em?.storeCache(this._cache, cached, res);
@@ -462,9 +534,29 @@ class QueryBuilder {
462
534
  await this.em.tryFlush(this.mainAlias.entityName, { flushMode: this.flushMode });
463
535
  let res = await this.execute('all', true);
464
536
  if (this._joinedProps.size > 0) {
465
- res = this.driver.mergeJoinedResult(res, this.mainAlias.metadata);
537
+ res = this.driver.mergeJoinedResult(res, this.mainAlias.metadata, [...this._joinedProps.values()]);
538
+ }
539
+ const entities = [];
540
+ function propagatePopulateHint(entity, hint) {
541
+ (0, core_1.helper)(entity).__serializationContext.populate ??= hint;
542
+ hint.forEach(pop => {
543
+ const value = entity[pop.field];
544
+ if (core_1.Utils.isEntity(value, true)) {
545
+ (0, core_1.helper)(value).populated();
546
+ propagatePopulateHint(value, pop.children ?? []);
547
+ }
548
+ else if (core_1.Utils.isCollection(value)) {
549
+ value.populated();
550
+ value.getItems(false).forEach(item => propagatePopulateHint(item, pop.children ?? []));
551
+ }
552
+ });
553
+ }
554
+ for (const r of res) {
555
+ const entity = this.em.map(this.mainAlias.entityName, r, { schema: this._schema });
556
+ propagatePopulateHint(entity, this._populate);
557
+ entities.push(entity);
466
558
  }
467
- return res.map(r => this.em.map(this.mainAlias.entityName, r, { schema: this._schema }));
559
+ return entities;
468
560
  }
469
561
  /**
470
562
  * Executes the query, returning the first result or null
@@ -482,17 +574,30 @@ class QueryBuilder {
482
574
  res = await this.execute('get', false);
483
575
  }
484
576
  else {
485
- const qb = this.clone();
577
+ const qb = this.type === undefined ? this : this.clone();
486
578
  qb.count(field, distinct ?? qb.hasToManyJoins()).limit(undefined).offset(undefined).orderBy([]);
487
579
  res = await qb.execute('get', false);
488
580
  }
489
581
  return res ? +res.count : 0;
490
582
  }
583
+ /**
584
+ * Executes the query, returning both array of results and total count query (without offset and limit).
585
+ */
586
+ async getResultAndCount() {
587
+ return Promise.all([
588
+ this.getResultList(),
589
+ this.getCount(),
590
+ ]);
591
+ }
491
592
  /**
492
593
  * Provides promise-like interface so we can await the QB instance.
493
594
  */
494
595
  then(onfulfilled, onrejected) {
495
- switch (this.type ?? enums_1.QueryType.SELECT) {
596
+ let type = this.type ?? enums_1.QueryType.SELECT;
597
+ if (this.flags.has(core_1.QueryFlag.UPDATE_SUB_QUERY) || this.flags.has(core_1.QueryFlag.DELETE_SUB_QUERY)) {
598
+ type = enums_1.QueryType.UPDATE;
599
+ }
600
+ switch (type) {
496
601
  case enums_1.QueryType.INSERT:
497
602
  case enums_1.QueryType.UPDATE:
498
603
  case enums_1.QueryType.DELETE:
@@ -515,7 +620,10 @@ class QueryBuilder {
515
620
  /* istanbul ignore next */
516
621
  alias = meta?.properties[f]?.fieldNames[0] ?? alias;
517
622
  }
518
- return qb.as(alias);
623
+ const ret = qb.as(alias);
624
+ // tag the instance, so it is possible to detect it easily
625
+ Object.defineProperty(ret, '__as', { enumerable: false, value: alias });
626
+ return ret;
519
627
  }
520
628
  clone() {
521
629
  const qb = new QueryBuilder(this.mainAlias.entityName, this.metadata, this.driver, this.context, this.mainAlias.aliasName, this.connectionType, this.em);
@@ -523,7 +631,8 @@ class QueryBuilder {
523
631
  // clone array/object properties
524
632
  const properties = [
525
633
  'flags', '_populate', '_populateWhere', '_populateMap', '_joins', '_joinedProps', '_cond', '_data', '_orderBy',
526
- '_schema', '_indexHint', '_cache', 'subQueries', 'lockMode', 'lockTables',
634
+ '_schema', '_indexHint', '_cache', 'subQueries', 'lockMode', 'lockTables', '_groupBy', '_having', '_returning',
635
+ '_comments', '_hintComments',
527
636
  ];
528
637
  properties.forEach(prop => qb[prop] = core_1.Utils.copy(this[prop]));
529
638
  /* istanbul ignore else */
@@ -551,14 +660,18 @@ class QueryBuilder {
551
660
  }
552
661
  return qb;
553
662
  }
554
- joinReference(field, alias, cond, type, path) {
663
+ joinReference(field, alias, cond, type, path, schema) {
555
664
  this.ensureNotFinalized();
556
665
  const [fromAlias, fromField] = this.helper.splitField(field);
557
- const entityName = this._aliases[fromAlias]?.entityName;
666
+ const q = (str) => `'${str}'`;
667
+ if (!this._aliases[fromAlias]) {
668
+ 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(', ')}.`);
669
+ }
670
+ const entityName = this._aliases[fromAlias].entityName;
558
671
  const meta = this.metadata.get(entityName);
559
672
  const prop = meta.properties[fromField];
560
673
  if (!prop) {
561
- throw new Error(`Trying to join ${field}, but ${fromField} is not a defined relation on ${meta.className}`);
674
+ throw new Error(`Trying to join ${q(field)}, but ${q(fromField)} is not a defined relation on ${meta.className}.`);
562
675
  }
563
676
  this.createAlias(prop.type, alias);
564
677
  cond = core_1.QueryHelper.processWhere({
@@ -570,26 +683,26 @@ class QueryBuilder {
570
683
  aliased: !this.type || [enums_1.QueryType.SELECT, enums_1.QueryType.COUNT].includes(this.type),
571
684
  });
572
685
  let aliasedName = `${fromAlias}.${prop.name}#${alias}`;
573
- path ?? (path = `${(Object.values(this._joins).find(j => j.alias === fromAlias)?.path ?? entityName)}.${prop.name}`);
574
- if (prop.reference === core_1.ReferenceType.ONE_TO_MANY) {
575
- this._joins[aliasedName] = this.helper.joinOneToReference(prop, fromAlias, alias, type, cond);
686
+ path ??= `${(Object.values(this._joins).find(j => j.alias === fromAlias)?.path ?? entityName)}.${prop.name}`;
687
+ if (prop.kind === core_1.ReferenceKind.ONE_TO_MANY) {
688
+ this._joins[aliasedName] = this.helper.joinOneToReference(prop, fromAlias, alias, type, cond, schema);
576
689
  }
577
- else if (prop.reference === core_1.ReferenceType.MANY_TO_MANY) {
690
+ else if (prop.kind === core_1.ReferenceKind.MANY_TO_MANY) {
578
691
  let pivotAlias = alias;
579
692
  if (type !== 'pivotJoin') {
580
693
  const oldPivotAlias = this.getAliasForJoinPath(path + '[pivot]');
581
694
  pivotAlias = oldPivotAlias ?? this.getNextAlias(prop.pivotEntity);
582
695
  aliasedName = `${fromAlias}.${prop.name}#${pivotAlias}`;
583
696
  }
584
- const joins = this.helper.joinManyToManyReference(prop, fromAlias, alias, pivotAlias, type, cond, path);
697
+ const joins = this.helper.joinManyToManyReference(prop, fromAlias, alias, pivotAlias, type, cond, path, schema);
585
698
  Object.assign(this._joins, joins);
586
699
  this.createAlias(prop.pivotEntity, pivotAlias);
587
700
  }
588
- else if (prop.reference === core_1.ReferenceType.ONE_TO_ONE) {
589
- this._joins[aliasedName] = this.helper.joinOneToReference(prop, fromAlias, alias, type, cond);
701
+ else if (prop.kind === core_1.ReferenceKind.ONE_TO_ONE) {
702
+ this._joins[aliasedName] = this.helper.joinOneToReference(prop, fromAlias, alias, type, cond, schema);
590
703
  }
591
704
  else { // MANY_TO_ONE
592
- this._joins[aliasedName] = this.helper.joinManyToOneReference(prop, fromAlias, alias, type, cond);
705
+ this._joins[aliasedName] = this.helper.joinManyToOneReference(prop, fromAlias, alias, type, cond, schema);
593
706
  }
594
707
  if (!this._joins[aliasedName].path && path) {
595
708
  this._joins[aliasedName].path = path;
@@ -599,17 +712,25 @@ class QueryBuilder {
599
712
  prepareFields(fields, type = 'where') {
600
713
  const ret = [];
601
714
  fields.forEach(field => {
715
+ const rawField = core_1.RawQueryFragment.getKnownFragment(field);
716
+ if (rawField) {
717
+ const sql = this.platform.formatQuery(rawField.sql, rawField.params);
718
+ ret.push(this.knex.raw(sql));
719
+ return;
720
+ }
602
721
  if (!core_1.Utils.isString(field)) {
603
- return ret.push(field);
722
+ ret.push(field);
723
+ return;
604
724
  }
605
725
  const join = Object.keys(this._joins).find(k => field === k.substring(0, k.indexOf('#')));
606
726
  if (join && type === 'where') {
607
- return ret.push(...this.helper.mapJoinColumns(this.type ?? enums_1.QueryType.SELECT, this._joins[join]));
727
+ ret.push(...this.helper.mapJoinColumns(this.type ?? enums_1.QueryType.SELECT, this._joins[join]));
728
+ return;
608
729
  }
609
730
  const [a, f] = this.helper.splitField(field);
610
731
  const prop = this.helper.getProperty(f, a);
611
732
  /* istanbul ignore next */
612
- if (prop && [core_1.ReferenceType.ONE_TO_MANY, core_1.ReferenceType.MANY_TO_MANY].includes(prop.reference)) {
733
+ if (prop && [core_1.ReferenceKind.ONE_TO_MANY, core_1.ReferenceKind.MANY_TO_MANY].includes(prop.kind)) {
613
734
  return;
614
735
  }
615
736
  if (prop?.embedded) {
@@ -617,7 +738,7 @@ class QueryBuilder {
617
738
  ret.push(fieldName);
618
739
  return;
619
740
  }
620
- if (prop?.reference === core_1.ReferenceType.EMBEDDED) {
741
+ if (prop?.kind === core_1.ReferenceKind.EMBEDDED) {
621
742
  if (prop.object) {
622
743
  ret.push(this.helper.mapper(prop.fieldNames[0], this.type));
623
744
  }
@@ -636,11 +757,15 @@ class QueryBuilder {
636
757
  }
637
758
  return;
638
759
  }
760
+ if (prop && prop.fieldNames.length > 1) {
761
+ ret.push(...prop.fieldNames.map(f => this.helper.mapper(f, this.type)));
762
+ return;
763
+ }
639
764
  ret.push(this.helper.mapper(field, this.type));
640
765
  });
641
766
  const meta = this.mainAlias.metadata;
642
767
  /* istanbul ignore next */
643
- const requiresSQLConversion = meta?.props.filter(p => p.customType?.convertToJSValueSQL) ?? [];
768
+ const requiresSQLConversion = meta?.props.filter(p => p.hasConvertToJSValueSQL) ?? [];
644
769
  if (this.flags.has(core_1.QueryFlag.CONVERT_CUSTOM_TYPES) && (fields.includes('*') || fields.includes(`${this.mainAlias.aliasName}.*`)) && requiresSQLConversion.length > 0) {
645
770
  requiresSQLConversion.forEach(p => ret.push(this.helper.mapper(p.name, this.type)));
646
771
  }
@@ -649,7 +774,7 @@ class QueryBuilder {
649
774
  const cols = this.helper.mapJoinColumns(this.type ?? enums_1.QueryType.SELECT, this._joins[f]);
650
775
  ret.push(...cols);
651
776
  }
652
- if (this._joins[f].prop.reference !== core_1.ReferenceType.ONE_TO_ONE && this._joins[f].inverseJoinColumns) {
777
+ if (this._joins[f].prop.kind !== core_1.ReferenceKind.ONE_TO_ONE && this._joins[f].inverseJoinColumns) {
653
778
  this._joins[f].inverseJoinColumns.forEach(inverseJoinColumn => {
654
779
  core_1.Utils.renameKey(this._cond, inverseJoinColumn, `${this._joins[f].alias}.${inverseJoinColumn}`);
655
780
  });
@@ -668,7 +793,7 @@ class QueryBuilder {
668
793
  }
669
794
  if (data) {
670
795
  if (core_1.Utils.isEntity(data)) {
671
- data = (0, core_1.helper)(data).toJSON();
796
+ data = this.em?.getComparator().prepareEntity(data) ?? (0, core_1.serialize)(data);
672
797
  }
673
798
  this._data = this.helper.processData(data, this.flags.has(core_1.QueryFlag.CONVERT_CUSTOM_TYPES));
674
799
  }
@@ -722,6 +847,24 @@ class QueryBuilder {
722
847
  }
723
848
  return qb;
724
849
  }
850
+ applyDiscriminatorCondition() {
851
+ const meta = this.mainAlias.metadata;
852
+ if (!meta?.discriminatorValue) {
853
+ return;
854
+ }
855
+ const types = Object.values(meta.root.discriminatorMap).map(cls => this.metadata.find(cls));
856
+ const children = [];
857
+ const lookUpChildren = (ret, type) => {
858
+ const children = types.filter(meta2 => meta2.extends === type);
859
+ children.forEach(m => lookUpChildren(ret, m.className));
860
+ ret.push(...children.filter(c => c.discriminatorValue));
861
+ return children;
862
+ };
863
+ lookUpChildren(children, meta.className);
864
+ this.andWhere({
865
+ [meta.root.discriminatorColumn]: children.length > 0 ? { $in: [meta.discriminatorValue, ...children.map(c => c.discriminatorValue)] } : meta.discriminatorValue,
866
+ });
867
+ }
725
868
  finalize() {
726
869
  if (this.finalized) {
727
870
  return;
@@ -730,10 +873,11 @@ class QueryBuilder {
730
873
  this.select('*');
731
874
  }
732
875
  const meta = this.mainAlias.metadata;
876
+ this.applyDiscriminatorCondition();
733
877
  if (meta && this.flags.has(core_1.QueryFlag.AUTO_JOIN_ONE_TO_ONE_OWNER)) {
734
878
  const relationsToPopulate = this._populate.map(({ field }) => field);
735
879
  meta.relations
736
- .filter(prop => prop.reference === core_1.ReferenceType.ONE_TO_ONE && !prop.owner && !relationsToPopulate.includes(prop.name))
880
+ .filter(prop => prop.kind === core_1.ReferenceKind.ONE_TO_ONE && !prop.owner && !relationsToPopulate.includes(prop.name))
737
881
  .map(prop => ({ field: prop.name }))
738
882
  .forEach(item => this._populate.push(item));
739
883
  }
@@ -742,12 +886,10 @@ class QueryBuilder {
742
886
  const aliasedField = `${fromAlias}.${fromField}`;
743
887
  const join = Object.keys(this._joins).find(k => `${aliasedField}#${this._joins[k].alias}` === k);
744
888
  if (join && this._joins[join] && this.helper.isOneToOneInverse(fromField)) {
745
- return this._populateMap[join] = this._joins[join].alias;
746
- }
747
- if (this.metadata.find(field)?.pivotTable) { // pivot table entity
748
- this.autoJoinPivotTable(field);
889
+ this._populateMap[join] = this._joins[join].alias;
890
+ return;
749
891
  }
750
- else if (meta && this.helper.isOneToOneInverse(fromField)) {
892
+ if (meta && this.helper.isOneToOneInverse(fromField)) {
751
893
  const prop = meta.properties[fromField];
752
894
  const alias = this.getNextAlias(prop.pivotEntity ?? prop.type);
753
895
  const aliasedName = `${fromAlias}.${prop.name}#${alias}`;
@@ -763,14 +905,20 @@ class QueryBuilder {
763
905
  const aliased = this.knex.ref(prop.fieldNames[0]).toString();
764
906
  return `${prop.formula(alias)} as ${aliased}`;
765
907
  })
766
- .filter(field => !this._fields.includes(field))
767
- .forEach(field => this.addSelect(field));
908
+ .filter(field => !this._fields.some(f => {
909
+ if (f instanceof core_1.RawQueryFragment) {
910
+ return f.sql === field && f.params.length === 0;
911
+ }
912
+ return f === field;
913
+ }))
914
+ .forEach(field => this._fields.push((0, core_1.raw)(field)));
768
915
  }
916
+ this.processPopulateWhere();
769
917
  core_1.QueryHelper.processObjectParams(this._data);
770
918
  core_1.QueryHelper.processObjectParams(this._cond);
771
919
  core_1.QueryHelper.processObjectParams(this._having);
772
- // automatically enable paginate flag when we detect to-many joins
773
- if (!this.flags.has(core_1.QueryFlag.DISABLE_PAGINATE) && this.hasToManyJoins()) {
920
+ // automatically enable paginate flag when we detect to-many joins, but only if there is no `group by` clause
921
+ if (!this.flags.has(core_1.QueryFlag.DISABLE_PAGINATE) && this._groupBy.length === 0 && this.hasToManyJoins()) {
774
922
  this.flags.add(core_1.QueryFlag.PAGINATE);
775
923
  }
776
924
  if (meta && this.flags.has(core_1.QueryFlag.PAGINATE) && (this._limit > 0 || this._offset > 0)) {
@@ -781,17 +929,54 @@ class QueryBuilder {
781
929
  }
782
930
  this.finalized = true;
783
931
  }
932
+ processPopulateWhere() {
933
+ if (this._populateWhere == null || this._populateWhere === core_1.PopulateHint.ALL) {
934
+ return;
935
+ }
936
+ const joins = Object.values(this._joins);
937
+ joins.forEach(join => {
938
+ join.cond_ = join.cond;
939
+ join.cond = {};
940
+ });
941
+ const replaceOnConditions = (cond) => {
942
+ Object.keys(cond).forEach(k => {
943
+ if (core_1.Utils.isOperator(k)) {
944
+ if (Array.isArray(cond[k])) {
945
+ return cond[k].forEach((c) => replaceOnConditions(c));
946
+ }
947
+ return replaceOnConditions(cond[k]);
948
+ }
949
+ const [a] = this.helper.splitField(k);
950
+ const join = joins.find(j => j.alias === a);
951
+ if (join) {
952
+ join.cond = { ...join.cond, [k]: cond[k] };
953
+ }
954
+ });
955
+ };
956
+ if (this._populateWhere === core_1.PopulateHint.INFER) {
957
+ replaceOnConditions(this._cond);
958
+ }
959
+ else if (typeof this._populateWhere === 'object') {
960
+ const cond = CriteriaNodeFactory_1.CriteriaNodeFactory
961
+ .createNode(this.metadata, this.mainAlias.entityName, this._populateWhere)
962
+ .process(this);
963
+ replaceOnConditions(cond);
964
+ }
965
+ }
784
966
  hasToManyJoins() {
785
967
  return Object.values(this._joins).some(join => {
786
- return [core_1.ReferenceType.ONE_TO_MANY, core_1.ReferenceType.MANY_TO_MANY].includes(join.prop.reference);
968
+ return [core_1.ReferenceKind.ONE_TO_MANY, core_1.ReferenceKind.MANY_TO_MANY].includes(join.prop.kind);
787
969
  });
788
970
  }
789
971
  wrapPaginateSubQuery(meta) {
790
972
  const pks = this.prepareFields(meta.primaryKeys, 'sub-query');
791
973
  const subQuery = this.clone().select(pks).groupBy(pks).limit(this._limit);
974
+ // revert the on conditions added via populateWhere, we want to apply those only once
975
+ Object.values(subQuery._joins).forEach(join => join.cond = join.cond_ ?? {});
792
976
  if (this._offset) {
793
977
  subQuery.offset(this._offset);
794
978
  }
979
+ const addToSelect = [];
795
980
  if (this._orderBy.length > 0) {
796
981
  const orderBy = [];
797
982
  for (const orderMap of this._orderBy) {
@@ -799,28 +984,38 @@ class QueryBuilder {
799
984
  const [a, f] = this.helper.splitField(field);
800
985
  const prop = this.helper.getProperty(f, a);
801
986
  const type = this.platform.castColumn(prop);
802
- orderBy.push({
803
- [`min(${this.ref(this.helper.mapper(field, this.type, undefined, null))}${type})`]: direction,
804
- });
987
+ const fieldName = this.helper.mapper(field, this.type, undefined, null);
988
+ if (!prop?.persist && !prop?.formula) {
989
+ addToSelect.push(fieldName);
990
+ }
991
+ orderBy.push({ [(0, core_1.raw)(`min(${this.knex.ref(fieldName)}${type})`)]: direction });
805
992
  }
806
993
  }
807
994
  subQuery.orderBy(orderBy);
808
995
  }
809
996
  subQuery.finalized = true;
810
997
  const knexQuery = subQuery.as(this.mainAlias.aliasName).clearSelect().select(pks);
998
+ if (addToSelect.length > 0) {
999
+ addToSelect.forEach(prop => {
1000
+ const field = this._fields.find(field => {
1001
+ if (typeof field === 'object' && field && '__as' in field) {
1002
+ return field.__as === prop;
1003
+ }
1004
+ // not perfect, but should work most of the time, ideally we should check only the alias (`... as alias`)
1005
+ return field.toString().includes(prop);
1006
+ });
1007
+ if (field) {
1008
+ knexQuery.select(field);
1009
+ }
1010
+ });
1011
+ }
811
1012
  // multiple sub-queries are needed to get around mysql limitations with order by + limit + where in + group by (o.O)
812
1013
  // https://stackoverflow.com/questions/17892762/mysql-this-version-of-mysql-doesnt-yet-support-limit-in-all-any-some-subqu
813
1014
  const subSubQuery = this.getKnex().select(pks).from(knexQuery);
1015
+ subSubQuery.__raw = true; // tag it as there is now way to check via `instanceof`
814
1016
  this._limit = undefined;
815
1017
  this._offset = undefined;
816
- const cond = this._cond;
817
1018
  this.select(this._fields).where({ [core_1.Utils.getPrimaryKeyHash(meta.primaryKeys)]: { $in: subSubQuery } });
818
- if (this._populateWhere === core_1.PopulateHint.INFER) {
819
- this.andWhere(cond);
820
- }
821
- else if (typeof this._populateWhere === 'object') {
822
- this.andWhere(this._populateWhere);
823
- }
824
1019
  }
825
1020
  wrapModifySubQuery(meta) {
826
1021
  const subQuery = this.clone();
@@ -834,17 +1029,6 @@ class QueryBuilder {
834
1029
  [core_1.Utils.getPrimaryKeyHash(meta.primaryKeys)]: { $in: subSubQuery },
835
1030
  });
836
1031
  }
837
- autoJoinPivotTable(field) {
838
- const pivotMeta = this.metadata.find(field);
839
- const owner = pivotMeta.relations[0];
840
- const inverse = pivotMeta.relations[1];
841
- const prop = this._cond[pivotMeta.name + '.' + owner.name] || this._orderBy[pivotMeta.name + '.' + owner.name] ? inverse : owner;
842
- const pivotAlias = this.getNextAlias(pivotMeta.name);
843
- this._joins[field] = this.helper.joinPivotTable(field, prop, this.mainAlias.aliasName, pivotAlias, 'leftJoin');
844
- core_1.Utils.renameKey(this._cond, `${field}.${owner.name}`, core_1.Utils.getPrimaryKeyHash(owner.fieldNames.map(fieldName => `${pivotAlias}.${fieldName}`)));
845
- core_1.Utils.renameKey(this._cond, `${field}.${inverse.name}`, core_1.Utils.getPrimaryKeyHash(inverse.fieldNames.map(fieldName => `${pivotAlias}.${fieldName}`)));
846
- this._populateMap[field] = this._joins[field].alias;
847
- }
848
1032
  getSchema(alias) {
849
1033
  const { metadata } = alias;
850
1034
  const metaSchema = metadata?.schema && metadata.schema !== '*' ? metadata.schema : undefined;
@@ -864,11 +1048,11 @@ class QueryBuilder {
864
1048
  fromSubQuery(target, aliasName) {
865
1049
  const subQuery = target.getKnexQuery();
866
1050
  const { entityName } = target.mainAlias;
867
- aliasName ?? (aliasName = this.getNextAlias(entityName));
1051
+ aliasName ??= this.getNextAlias(entityName);
868
1052
  this.createMainAlias(entityName, aliasName, subQuery);
869
1053
  }
870
1054
  fromEntityName(entityName, aliasName) {
871
- aliasName ?? (aliasName = this._mainAlias?.aliasName ?? this.getNextAlias(entityName));
1055
+ aliasName ??= this._mainAlias?.aliasName ?? this.getNextAlias(entityName);
872
1056
  this.createMainAlias(entityName, aliasName);
873
1057
  }
874
1058
  createQueryBuilderHelper() {
@@ -885,5 +1069,30 @@ class QueryBuilder {
885
1069
  throw new Error('This QueryBuilder instance is already finalized, clone it first if you want to modify it.');
886
1070
  }
887
1071
  }
1072
+ /* istanbul ignore next */
1073
+ [util_1.inspect.custom](depth) {
1074
+ const object = { ...this };
1075
+ const hidden = ['metadata', 'driver', 'context', 'platform', 'knex', 'type'];
1076
+ Object.keys(object).filter(k => k.startsWith('_')).forEach(k => delete object[k]);
1077
+ Object.keys(object).filter(k => object[k] == null).forEach(k => delete object[k]);
1078
+ hidden.forEach(k => delete object[k]);
1079
+ let prefix = this.type ? this.type.substring(0, 1) + this.type.toLowerCase().substring(1) : '';
1080
+ if (this._data) {
1081
+ object.data = this._data;
1082
+ }
1083
+ if (this._schema) {
1084
+ object.schema = this._schema;
1085
+ }
1086
+ if (!core_1.Utils.isEmpty(this._cond)) {
1087
+ object.where = this._cond;
1088
+ }
1089
+ if (this._onConflict?.[0]) {
1090
+ prefix = 'Upsert';
1091
+ object.onConflict = this._onConflict[0];
1092
+ }
1093
+ const name = this._mainAlias ? `${prefix}QueryBuilder<${this._mainAlias?.entityName}>` : 'QueryBuilder';
1094
+ const ret = (0, util_1.inspect)(object, { depth });
1095
+ return ret === '[Object]' ? `[${name}]` : name + ' ' + ret;
1096
+ }
888
1097
  }
889
1098
  exports.QueryBuilder = QueryBuilder;