@mikro-orm/knex 6.0.0-dev.11 → 6.0.0-dev.110

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 +16 -17
  4. package/AbstractSqlDriver.js +271 -113
  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 +39 -19
  24. package/query/QueryBuilder.js +331 -111
  25. package/query/QueryBuilderHelper.d.ts +9 -13
  26. package/query/QueryBuilderHelper.js +153 -107
  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 +54 -29
  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 +13 -7
@@ -26,6 +26,12 @@ const CriteriaNodeFactory_1 = require("./CriteriaNodeFactory");
26
26
  * ```
27
27
  */
28
28
  class QueryBuilder {
29
+ metadata;
30
+ driver;
31
+ context;
32
+ connectionType;
33
+ em;
34
+ logging;
29
35
  get mainAlias() {
30
36
  this.ensureFromClause();
31
37
  return this._mainAlias;
@@ -37,31 +43,56 @@ class QueryBuilder {
37
43
  this.ensureFromClause();
38
44
  return this._helper;
39
45
  }
46
+ /** @internal */
47
+ type;
48
+ /** @internal */
49
+ _fields;
50
+ /** @internal */
51
+ _populate = [];
52
+ /** @internal */
53
+ _populateWhere;
54
+ /** @internal */
55
+ _populateMap = {};
56
+ aliasCounter = 0;
57
+ flags = new Set([core_1.QueryFlag.CONVERT_CUSTOM_TYPES]);
58
+ finalized = false;
59
+ _joins = {};
60
+ _explicitAlias = false;
61
+ _schema;
62
+ _cond = {};
63
+ _data;
64
+ _orderBy = [];
65
+ _groupBy = [];
66
+ _having = {};
67
+ _returning;
68
+ _onConflict;
69
+ _limit;
70
+ _offset;
71
+ _distinctOn;
72
+ _joinedProps = new Map();
73
+ _cache;
74
+ _indexHint;
75
+ _comments = [];
76
+ _hintComments = [];
77
+ flushMode;
78
+ lockMode;
79
+ lockTables;
80
+ subQueries = {};
81
+ _mainAlias;
82
+ _aliases = {};
83
+ _helper;
84
+ platform;
85
+ knex;
40
86
  /**
41
87
  * @internal
42
88
  */
43
- constructor(entityName, metadata, driver, context, alias, connectionType, em) {
89
+ constructor(entityName, metadata, driver, context, alias, connectionType, em, logging) {
44
90
  this.metadata = metadata;
45
91
  this.driver = driver;
46
92
  this.context = context;
47
93
  this.connectionType = connectionType;
48
94
  this.em = em;
49
- /** @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 = {};
95
+ this.logging = logging;
65
96
  this.platform = this.driver.getPlatform();
66
97
  this.knex = this.driver.getConnection(this.connectionType).getKnex();
67
98
  if (alias) {
@@ -116,27 +147,39 @@ class QueryBuilder {
116
147
  this._fields = this.mainAlias.metadata.primaryKeys;
117
148
  }
118
149
  else {
119
- this._fields = [this.raw('*')];
150
+ this._fields = [(0, core_1.raw)('*')];
120
151
  }
121
152
  if (distinct) {
122
153
  this.flags.add(core_1.QueryFlag.DISTINCT);
123
154
  }
124
155
  return this.init(enums_1.QueryType.COUNT);
125
156
  }
126
- join(field, alias, cond = {}, type = 'innerJoin', path) {
127
- 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);
159
+ return this;
160
+ }
161
+ innerJoin(field, alias, cond = {}, schema) {
162
+ this.join(field, alias, cond, 'innerJoin', undefined, schema);
128
163
  return this;
129
164
  }
130
- leftJoin(field, alias, cond = {}) {
131
- return this.join(field, alias, cond, 'leftJoin');
165
+ leftJoin(field, alias, cond = {}, schema) {
166
+ return this.join(field, alias, cond, 'leftJoin', undefined, schema);
132
167
  }
133
- joinAndSelect(field, alias, cond = {}, type = 'innerJoin', path) {
168
+ joinAndSelect(field, alias, cond = {}, type = 'innerJoin', path, fields, schema) {
134
169
  if (!this.type) {
135
170
  this.select('*');
136
171
  }
137
- const prop = this.joinReference(field, alias, cond, type, path);
138
- this.addSelect(this.getFieldsForJoinedLoad(prop, alias));
172
+ let subquery;
173
+ if (Array.isArray(field)) {
174
+ subquery = field[1] instanceof QueryBuilder ? field[1].getFormattedQuery() : field[1].toString();
175
+ field = field[0];
176
+ }
177
+ const prop = this.joinReference(field, alias, cond, type, path, schema);
139
178
  const [fromAlias] = this.helper.splitField(field);
179
+ if (subquery) {
180
+ this._joins[`${fromAlias}.${prop.name}#${alias}`].subquery = subquery;
181
+ }
182
+ this.addSelect(this.getFieldsForJoinedLoad(prop, alias, fields));
140
183
  const populate = this._joinedProps.get(fromAlias);
141
184
  const item = { field: prop.name, strategy: core_1.LoadStrategy.JOINED, children: [] };
142
185
  if (populate) {
@@ -148,13 +191,16 @@ class QueryBuilder {
148
191
  this._joinedProps.set(alias, item);
149
192
  return this;
150
193
  }
151
- leftJoinAndSelect(field, alias, cond = {}) {
152
- return this.joinAndSelect(field, alias, cond, 'leftJoin');
194
+ leftJoinAndSelect(field, alias, cond = {}, fields, schema) {
195
+ return this.joinAndSelect(field, alias, cond, 'leftJoin', undefined, fields, schema);
153
196
  }
154
- getFieldsForJoinedLoad(prop, alias) {
197
+ innerJoinAndSelect(field, alias, cond = {}, fields, schema) {
198
+ return this.joinAndSelect(field, alias, cond, 'innerJoin', undefined, fields, schema);
199
+ }
200
+ getFieldsForJoinedLoad(prop, alias, explicitFields) {
155
201
  const fields = [];
156
202
  prop.targetMeta.props
157
- .filter(prop => this.platform.shouldHaveColumn(prop, this._populate))
203
+ .filter(prop => explicitFields ? explicitFields.includes(prop.name) || prop.primary : this.platform.shouldHaveColumn(prop, this._populate))
158
204
  .forEach(prop => fields.push(...this.driver.mapPropToFieldNames(this, prop, alias)));
159
205
  return fields;
160
206
  }
@@ -165,9 +211,15 @@ class QueryBuilder {
165
211
  }
166
212
  where(cond, params, operator) {
167
213
  this.ensureNotFinalized();
168
- if (core_1.Utils.isString(cond)) {
169
- cond = { [`(${cond})`]: core_1.Utils.asArray(params) };
170
- operator = operator || '$and';
214
+ const rawField = core_1.RawQueryFragment.getKnownFragment(cond);
215
+ if (rawField) {
216
+ const sql = this.platform.formatQuery(rawField.sql, rawField.params);
217
+ cond = { [(0, core_1.raw)(`(${sql})`)]: core_1.Utils.asArray(params) };
218
+ operator ??= '$and';
219
+ }
220
+ else if (core_1.Utils.isString(cond)) {
221
+ cond = { [(0, core_1.raw)(`(${cond})`, core_1.Utils.asArray(params))]: [] };
222
+ operator ??= '$and';
171
223
  }
172
224
  else {
173
225
  cond = core_1.QueryHelper.processWhere({
@@ -235,15 +287,22 @@ class QueryBuilder {
235
287
  having(cond = {}, params) {
236
288
  this.ensureNotFinalized();
237
289
  if (core_1.Utils.isString(cond)) {
238
- cond = { [`(${cond})`]: core_1.Utils.asArray(params) };
290
+ cond = { [(0, core_1.raw)(`(${cond})`, params)]: [] };
239
291
  }
240
292
  this._having = CriteriaNodeFactory_1.CriteriaNodeFactory.createNode(this.metadata, this.mainAlias.entityName, cond).process(this);
241
293
  return this;
242
294
  }
243
295
  onConflict(fields = []) {
296
+ const meta = this.mainAlias.metadata;
244
297
  this.ensureNotFinalized();
245
- this._onConflict = this._onConflict || [];
246
- this._onConflict.push({ fields: core_1.Utils.asArray(fields).map(f => f.toString()) });
298
+ this._onConflict ??= [];
299
+ this._onConflict.push({
300
+ fields: core_1.Utils.asArray(fields).flatMap(f => {
301
+ const key = f.toString();
302
+ /* istanbul ignore next */
303
+ return meta.properties[key]?.fieldNames ?? [key];
304
+ }),
305
+ });
247
306
  return this;
248
307
  }
249
308
  ignore() {
@@ -263,6 +322,10 @@ class QueryBuilder {
263
322
  this._onConflict[this._onConflict.length - 1].merge = data;
264
323
  return this;
265
324
  }
325
+ returning(fields) {
326
+ this._returning = fields != null ? core_1.Utils.asArray(fields) : fields;
327
+ return this;
328
+ }
266
329
  /**
267
330
  * @internal
268
331
  */
@@ -272,17 +335,6 @@ class QueryBuilder {
272
335
  this._populateWhere = populateWhere;
273
336
  return this;
274
337
  }
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
338
  limit(limit, offset = 0) {
287
339
  this.ensureNotFinalized();
288
340
  this._limit = limit;
@@ -338,6 +390,24 @@ class QueryBuilder {
338
390
  this._indexHint = sql;
339
391
  return this;
340
392
  }
393
+ /**
394
+ * Prepend comment to the sql query using the syntax `/* ... *‍/`. Some characters are forbidden such as `/*, *‍/` and `?`.
395
+ */
396
+ comment(comment) {
397
+ this.ensureNotFinalized();
398
+ this._comments.push(...core_1.Utils.asArray(comment));
399
+ return this;
400
+ }
401
+ /**
402
+ * Add hints to the query using comment-like syntax `/*+ ... *‍/`. MySQL and Oracle use this syntax for optimizer hints.
403
+ * Also various DB proxies and routers use this syntax to pass hints to alter their behavior. In other dialects the hints
404
+ * are ignored as simple comments.
405
+ */
406
+ hintComment(comment) {
407
+ this.ensureNotFinalized();
408
+ this._hintComments.push(...core_1.Utils.asArray(comment));
409
+ return this;
410
+ }
341
411
  from(target, aliasName) {
342
412
  this.ensureNotFinalized();
343
413
  if (target instanceof QueryBuilder) {
@@ -355,17 +425,21 @@ class QueryBuilder {
355
425
  getKnexQuery() {
356
426
  this.finalize();
357
427
  const qb = this.getQueryBase();
428
+ qb.__raw = true; // tag it as there is now way to check via `instanceof`
358
429
  core_1.Utils.runIfNotEmpty(() => this.helper.appendQueryCondition(this.type ?? enums_1.QueryType.SELECT, this._cond, qb), this._cond && !this._onConflict);
359
430
  core_1.Utils.runIfNotEmpty(() => qb.groupBy(this.prepareFields(this._groupBy, 'groupBy')), this._groupBy);
360
431
  core_1.Utils.runIfNotEmpty(() => this.helper.appendQueryCondition(this.type ?? enums_1.QueryType.SELECT, this._having, qb, undefined, 'having'), this._having);
361
432
  core_1.Utils.runIfNotEmpty(() => {
362
433
  const queryOrder = this.helper.getQueryOrder(this.type ?? enums_1.QueryType.SELECT, this._orderBy, this._populateMap);
363
434
  if (queryOrder) {
364
- return qb.orderByRaw(queryOrder);
435
+ qb.orderByRaw(queryOrder);
436
+ return;
365
437
  }
366
438
  }, this._orderBy);
367
439
  core_1.Utils.runIfNotEmpty(() => qb.limit(this._limit), this._limit != null);
368
440
  core_1.Utils.runIfNotEmpty(() => qb.offset(this._offset), this._offset);
441
+ core_1.Utils.runIfNotEmpty(() => this._comments.forEach(comment => qb.comment(comment)), this._comments);
442
+ core_1.Utils.runIfNotEmpty(() => this._hintComments.forEach(comment => qb.hintComment(comment)), this._hintComments);
369
443
  core_1.Utils.runIfNotEmpty(() => this.helper.appendOnConflictClause(this.type ?? enums_1.QueryType.SELECT, this._onConflict, qb), this._onConflict);
370
444
  if (this.type === enums_1.QueryType.TRUNCATE && this.platform.usesCascadeStatement()) {
371
445
  return this.knex.raw(qb.toSQL().toNative().sql + ' cascade');
@@ -373,26 +447,35 @@ class QueryBuilder {
373
447
  if (this.lockMode) {
374
448
  this.helper.getLockSQL(qb, this.lockMode, this.lockTables);
375
449
  }
376
- this.helper.finalize(this.type ?? enums_1.QueryType.SELECT, qb, this.mainAlias.metadata);
450
+ this.helper.finalize(this.type ?? enums_1.QueryType.SELECT, qb, this.mainAlias.metadata, this._data, this._returning);
377
451
  return qb;
378
452
  }
379
453
  /**
380
454
  * Returns the query with parameters as wildcards.
381
455
  */
382
456
  getQuery() {
383
- return this.getKnexQuery().toSQL().toNative().sql;
457
+ return this.toQuery().sql;
458
+ }
459
+ #query;
460
+ toQuery() {
461
+ if (this.#query) {
462
+ return this.#query;
463
+ }
464
+ const sql = this.getKnexQuery().toSQL();
465
+ const query = sql.toNative();
466
+ return this.#query = { sql: query.sql, params: query.bindings ?? [], _sql: sql };
384
467
  }
385
468
  /**
386
469
  * Returns the list of all parameters for this query.
387
470
  */
388
471
  getParams() {
389
- return this.getKnexQuery().toSQL().toNative().bindings;
472
+ return this.toQuery().params;
390
473
  }
391
474
  /**
392
475
  * Returns raw interpolated query string with all the parameters inlined.
393
476
  */
394
477
  getFormattedQuery() {
395
- const query = this.getKnexQuery().toSQL();
478
+ const query = this.toQuery()._sql;
396
479
  return this.platform.formatQuery(query.sql, query.bindings);
397
480
  }
398
481
  /**
@@ -434,7 +517,7 @@ class QueryBuilder {
434
517
  return cached.data;
435
518
  }
436
519
  const type = this.connectionType || (method === 'run' ? 'write' : 'read');
437
- const res = await this.driver.getConnection(type).execute(query.sql, query.bindings, method, this.context);
520
+ const res = await this.driver.getConnection(type).execute(query.sql, query.bindings, method, this.context, this.logging);
438
521
  const meta = this.mainAlias.metadata;
439
522
  if (!mapResults || !meta) {
440
523
  await this.em?.storeCache(this._cache, cached, res);
@@ -463,9 +546,29 @@ class QueryBuilder {
463
546
  await this.em.tryFlush(this.mainAlias.entityName, { flushMode: this.flushMode });
464
547
  let res = await this.execute('all', true);
465
548
  if (this._joinedProps.size > 0) {
466
- res = this.driver.mergeJoinedResult(res, this.mainAlias.metadata);
549
+ res = this.driver.mergeJoinedResult(res, this.mainAlias.metadata, [...this._joinedProps.values()]);
550
+ }
551
+ const entities = [];
552
+ function propagatePopulateHint(entity, hint) {
553
+ (0, core_1.helper)(entity).__serializationContext.populate ??= hint;
554
+ hint.forEach(pop => {
555
+ const value = entity[pop.field];
556
+ if (core_1.Utils.isEntity(value, true)) {
557
+ (0, core_1.helper)(value).populated();
558
+ propagatePopulateHint(value, pop.children ?? []);
559
+ }
560
+ else if (core_1.Utils.isCollection(value)) {
561
+ value.populated();
562
+ value.getItems(false).forEach(item => propagatePopulateHint(item, pop.children ?? []));
563
+ }
564
+ });
565
+ }
566
+ for (const r of res) {
567
+ const entity = this.em.map(this.mainAlias.entityName, r, { schema: this._schema });
568
+ propagatePopulateHint(entity, this._populate);
569
+ entities.push(entity);
467
570
  }
468
- return res.map(r => this.em.map(this.mainAlias.entityName, r, { schema: this._schema }));
571
+ return entities;
469
572
  }
470
573
  /**
471
574
  * Executes the query, returning the first result or null
@@ -483,17 +586,30 @@ class QueryBuilder {
483
586
  res = await this.execute('get', false);
484
587
  }
485
588
  else {
486
- const qb = this.clone();
589
+ const qb = this.type === undefined ? this : this.clone();
487
590
  qb.count(field, distinct ?? qb.hasToManyJoins()).limit(undefined).offset(undefined).orderBy([]);
488
591
  res = await qb.execute('get', false);
489
592
  }
490
593
  return res ? +res.count : 0;
491
594
  }
595
+ /**
596
+ * Executes the query, returning both array of results and total count query (without offset and limit).
597
+ */
598
+ async getResultAndCount() {
599
+ return Promise.all([
600
+ this.getResultList(),
601
+ this.getCount(),
602
+ ]);
603
+ }
492
604
  /**
493
605
  * Provides promise-like interface so we can await the QB instance.
494
606
  */
495
607
  then(onfulfilled, onrejected) {
496
- switch (this.type ?? enums_1.QueryType.SELECT) {
608
+ let type = this.type ?? enums_1.QueryType.SELECT;
609
+ if (this.flags.has(core_1.QueryFlag.UPDATE_SUB_QUERY) || this.flags.has(core_1.QueryFlag.DELETE_SUB_QUERY)) {
610
+ type = enums_1.QueryType.UPDATE;
611
+ }
612
+ switch (type) {
497
613
  case enums_1.QueryType.INSERT:
498
614
  case enums_1.QueryType.UPDATE:
499
615
  case enums_1.QueryType.DELETE:
@@ -516,7 +632,10 @@ class QueryBuilder {
516
632
  /* istanbul ignore next */
517
633
  alias = meta?.properties[f]?.fieldNames[0] ?? alias;
518
634
  }
519
- return qb.as(alias);
635
+ const ret = qb.as(alias);
636
+ // tag the instance, so it is possible to detect it easily
637
+ Object.defineProperty(ret, '__as', { enumerable: false, value: alias });
638
+ return ret;
520
639
  }
521
640
  clone() {
522
641
  const qb = new QueryBuilder(this.mainAlias.entityName, this.metadata, this.driver, this.context, this.mainAlias.aliasName, this.connectionType, this.em);
@@ -524,7 +643,8 @@ class QueryBuilder {
524
643
  // clone array/object properties
525
644
  const properties = [
526
645
  'flags', '_populate', '_populateWhere', '_populateMap', '_joins', '_joinedProps', '_cond', '_data', '_orderBy',
527
- '_schema', '_indexHint', '_cache', 'subQueries', 'lockMode', 'lockTables',
646
+ '_schema', '_indexHint', '_cache', 'subQueries', 'lockMode', 'lockTables', '_groupBy', '_having', '_returning',
647
+ '_comments', '_hintComments',
528
648
  ];
529
649
  properties.forEach(prop => qb[prop] = core_1.Utils.copy(this[prop]));
530
650
  /* istanbul ignore else */
@@ -552,14 +672,39 @@ class QueryBuilder {
552
672
  }
553
673
  return qb;
554
674
  }
555
- joinReference(field, alias, cond, type, path) {
675
+ joinReference(field, alias, cond, type, path, schema) {
556
676
  this.ensureNotFinalized();
677
+ if (typeof field === 'object') {
678
+ const prop = {
679
+ name: '__subquery__',
680
+ kind: core_1.ReferenceKind.MANY_TO_ONE,
681
+ };
682
+ if (field instanceof QueryBuilder) {
683
+ prop.type = field.mainAlias.entityName;
684
+ prop.targetMeta = field.mainAlias.metadata;
685
+ field = field.getKnexQuery();
686
+ }
687
+ this._joins[`${this.alias}.${prop.name}#${alias}`] = {
688
+ prop,
689
+ alias,
690
+ type,
691
+ cond,
692
+ schema,
693
+ subquery: field.toString(),
694
+ ownerAlias: this.alias,
695
+ };
696
+ return prop;
697
+ }
557
698
  const [fromAlias, fromField] = this.helper.splitField(field);
558
- const entityName = this._aliases[fromAlias]?.entityName;
699
+ const q = (str) => `'${str}'`;
700
+ if (!this._aliases[fromAlias]) {
701
+ 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(', ')}.`);
702
+ }
703
+ const entityName = this._aliases[fromAlias].entityName;
559
704
  const meta = this.metadata.get(entityName);
560
705
  const prop = meta.properties[fromField];
561
706
  if (!prop) {
562
- throw new Error(`Trying to join ${field}, but ${fromField} is not a defined relation on ${meta.className}`);
707
+ throw new Error(`Trying to join ${q(field)}, but ${q(fromField)} is not a defined relation on ${meta.className}.`);
563
708
  }
564
709
  this.createAlias(prop.type, alias);
565
710
  cond = core_1.QueryHelper.processWhere({
@@ -571,26 +716,26 @@ class QueryBuilder {
571
716
  aliased: !this.type || [enums_1.QueryType.SELECT, enums_1.QueryType.COUNT].includes(this.type),
572
717
  });
573
718
  let aliasedName = `${fromAlias}.${prop.name}#${alias}`;
574
- path ?? (path = `${(Object.values(this._joins).find(j => j.alias === fromAlias)?.path ?? entityName)}.${prop.name}`);
575
- if (prop.reference === core_1.ReferenceType.ONE_TO_MANY) {
576
- this._joins[aliasedName] = this.helper.joinOneToReference(prop, fromAlias, alias, type, cond);
719
+ path ??= `${(Object.values(this._joins).find(j => j.alias === fromAlias)?.path ?? entityName)}.${prop.name}`;
720
+ if (prop.kind === core_1.ReferenceKind.ONE_TO_MANY) {
721
+ this._joins[aliasedName] = this.helper.joinOneToReference(prop, fromAlias, alias, type, cond, schema);
577
722
  }
578
- else if (prop.reference === core_1.ReferenceType.MANY_TO_MANY) {
723
+ else if (prop.kind === core_1.ReferenceKind.MANY_TO_MANY) {
579
724
  let pivotAlias = alias;
580
725
  if (type !== 'pivotJoin') {
581
726
  const oldPivotAlias = this.getAliasForJoinPath(path + '[pivot]');
582
727
  pivotAlias = oldPivotAlias ?? this.getNextAlias(prop.pivotEntity);
583
728
  aliasedName = `${fromAlias}.${prop.name}#${pivotAlias}`;
584
729
  }
585
- const joins = this.helper.joinManyToManyReference(prop, fromAlias, alias, pivotAlias, type, cond, path);
730
+ const joins = this.helper.joinManyToManyReference(prop, fromAlias, alias, pivotAlias, type, cond, path, schema);
586
731
  Object.assign(this._joins, joins);
587
732
  this.createAlias(prop.pivotEntity, pivotAlias);
588
733
  }
589
- else if (prop.reference === core_1.ReferenceType.ONE_TO_ONE) {
590
- this._joins[aliasedName] = this.helper.joinOneToReference(prop, fromAlias, alias, type, cond);
734
+ else if (prop.kind === core_1.ReferenceKind.ONE_TO_ONE) {
735
+ this._joins[aliasedName] = this.helper.joinOneToReference(prop, fromAlias, alias, type, cond, schema);
591
736
  }
592
737
  else { // MANY_TO_ONE
593
- this._joins[aliasedName] = this.helper.joinManyToOneReference(prop, fromAlias, alias, type, cond);
738
+ this._joins[aliasedName] = this.helper.joinManyToOneReference(prop, fromAlias, alias, type, cond, schema);
594
739
  }
595
740
  if (!this._joins[aliasedName].path && path) {
596
741
  this._joins[aliasedName].path = path;
@@ -600,25 +745,34 @@ class QueryBuilder {
600
745
  prepareFields(fields, type = 'where') {
601
746
  const ret = [];
602
747
  fields.forEach(field => {
748
+ const rawField = core_1.RawQueryFragment.getKnownFragment(field);
749
+ if (rawField) {
750
+ const sql = this.platform.formatQuery(rawField.sql, rawField.params);
751
+ ret.push(this.knex.raw(sql));
752
+ return;
753
+ }
603
754
  if (!core_1.Utils.isString(field)) {
604
- return ret.push(field);
755
+ ret.push(field);
756
+ return;
605
757
  }
606
758
  const join = Object.keys(this._joins).find(k => field === k.substring(0, k.indexOf('#')));
607
759
  if (join && type === 'where') {
608
- return ret.push(...this.helper.mapJoinColumns(this.type ?? enums_1.QueryType.SELECT, this._joins[join]));
760
+ ret.push(...this.helper.mapJoinColumns(this.type ?? enums_1.QueryType.SELECT, this._joins[join]));
761
+ return;
609
762
  }
610
763
  const [a, f] = this.helper.splitField(field);
611
764
  const prop = this.helper.getProperty(f, a);
612
765
  /* istanbul ignore next */
613
- if (prop && [core_1.ReferenceType.ONE_TO_MANY, core_1.ReferenceType.MANY_TO_MANY].includes(prop.reference)) {
766
+ if (prop && [core_1.ReferenceKind.ONE_TO_MANY, core_1.ReferenceKind.MANY_TO_MANY].includes(prop.kind)) {
614
767
  return;
615
768
  }
616
769
  if (prop?.embedded) {
617
- const fieldName = this.helper.mapper(prop.fieldNames[0], this.type);
770
+ const name = this._aliases[a] ? `${a}.${prop.fieldNames[0]}` : prop.fieldNames[0];
771
+ const fieldName = this.helper.mapper(name, this.type);
618
772
  ret.push(fieldName);
619
773
  return;
620
774
  }
621
- if (prop?.reference === core_1.ReferenceType.EMBEDDED) {
775
+ if (prop?.kind === core_1.ReferenceKind.EMBEDDED) {
622
776
  if (prop.object) {
623
777
  ret.push(this.helper.mapper(prop.fieldNames[0], this.type));
624
778
  }
@@ -637,11 +791,15 @@ class QueryBuilder {
637
791
  }
638
792
  return;
639
793
  }
794
+ if (prop && prop.fieldNames.length > 1) {
795
+ ret.push(...prop.fieldNames.map(f => this.helper.mapper(f, this.type)));
796
+ return;
797
+ }
640
798
  ret.push(this.helper.mapper(field, this.type));
641
799
  });
642
800
  const meta = this.mainAlias.metadata;
643
801
  /* istanbul ignore next */
644
- const requiresSQLConversion = meta?.props.filter(p => p.customType?.convertToJSValueSQL) ?? [];
802
+ const requiresSQLConversion = meta?.props.filter(p => p.hasConvertToJSValueSQL) ?? [];
645
803
  if (this.flags.has(core_1.QueryFlag.CONVERT_CUSTOM_TYPES) && (fields.includes('*') || fields.includes(`${this.mainAlias.aliasName}.*`)) && requiresSQLConversion.length > 0) {
646
804
  requiresSQLConversion.forEach(p => ret.push(this.helper.mapper(p.name, this.type)));
647
805
  }
@@ -650,7 +808,7 @@ class QueryBuilder {
650
808
  const cols = this.helper.mapJoinColumns(this.type ?? enums_1.QueryType.SELECT, this._joins[f]);
651
809
  ret.push(...cols);
652
810
  }
653
- if (this._joins[f].prop.reference !== core_1.ReferenceType.ONE_TO_ONE && this._joins[f].inverseJoinColumns) {
811
+ if (this._joins[f].prop.kind !== core_1.ReferenceKind.ONE_TO_ONE && this._joins[f].inverseJoinColumns) {
654
812
  this._joins[f].inverseJoinColumns.forEach(inverseJoinColumn => {
655
813
  core_1.Utils.renameKey(this._cond, inverseJoinColumn, `${this._joins[f].alias}.${inverseJoinColumn}`);
656
814
  });
@@ -669,7 +827,7 @@ class QueryBuilder {
669
827
  }
670
828
  if (data) {
671
829
  if (core_1.Utils.isEntity(data)) {
672
- data = (0, core_1.helper)(data).toJSON();
830
+ data = this.em?.getComparator().prepareEntity(data) ?? (0, core_1.serialize)(data);
673
831
  }
674
832
  this._data = this.helper.processData(data, this.flags.has(core_1.QueryFlag.CONVERT_CUSTOM_TYPES));
675
833
  }
@@ -723,6 +881,24 @@ class QueryBuilder {
723
881
  }
724
882
  return qb;
725
883
  }
884
+ applyDiscriminatorCondition() {
885
+ const meta = this.mainAlias.metadata;
886
+ if (!meta?.discriminatorValue) {
887
+ return;
888
+ }
889
+ const types = Object.values(meta.root.discriminatorMap).map(cls => this.metadata.find(cls));
890
+ const children = [];
891
+ const lookUpChildren = (ret, type) => {
892
+ const children = types.filter(meta2 => meta2.extends === type);
893
+ children.forEach(m => lookUpChildren(ret, m.className));
894
+ ret.push(...children.filter(c => c.discriminatorValue));
895
+ return children;
896
+ };
897
+ lookUpChildren(children, meta.className);
898
+ this.andWhere({
899
+ [meta.root.discriminatorColumn]: children.length > 0 ? { $in: [meta.discriminatorValue, ...children.map(c => c.discriminatorValue)] } : meta.discriminatorValue,
900
+ });
901
+ }
726
902
  finalize() {
727
903
  if (this.finalized) {
728
904
  return;
@@ -731,10 +907,11 @@ class QueryBuilder {
731
907
  this.select('*');
732
908
  }
733
909
  const meta = this.mainAlias.metadata;
910
+ this.applyDiscriminatorCondition();
734
911
  if (meta && this.flags.has(core_1.QueryFlag.AUTO_JOIN_ONE_TO_ONE_OWNER)) {
735
912
  const relationsToPopulate = this._populate.map(({ field }) => field);
736
913
  meta.relations
737
- .filter(prop => prop.reference === core_1.ReferenceType.ONE_TO_ONE && !prop.owner && !relationsToPopulate.includes(prop.name))
914
+ .filter(prop => prop.kind === core_1.ReferenceKind.ONE_TO_ONE && !prop.owner && !relationsToPopulate.includes(prop.name))
738
915
  .map(prop => ({ field: prop.name }))
739
916
  .forEach(item => this._populate.push(item));
740
917
  }
@@ -743,12 +920,10 @@ class QueryBuilder {
743
920
  const aliasedField = `${fromAlias}.${fromField}`;
744
921
  const join = Object.keys(this._joins).find(k => `${aliasedField}#${this._joins[k].alias}` === k);
745
922
  if (join && this._joins[join] && this.helper.isOneToOneInverse(fromField)) {
746
- return this._populateMap[join] = this._joins[join].alias;
747
- }
748
- if (this.metadata.find(field)?.pivotTable) { // pivot table entity
749
- this.autoJoinPivotTable(field);
923
+ this._populateMap[join] = this._joins[join].alias;
924
+ return;
750
925
  }
751
- else if (meta && this.helper.isOneToOneInverse(fromField)) {
926
+ if (meta && this.helper.isOneToOneInverse(fromField)) {
752
927
  const prop = meta.properties[fromField];
753
928
  const alias = this.getNextAlias(prop.pivotEntity ?? prop.type);
754
929
  const aliasedName = `${fromAlias}.${prop.name}#${alias}`;
@@ -764,14 +939,20 @@ class QueryBuilder {
764
939
  const aliased = this.knex.ref(prop.fieldNames[0]).toString();
765
940
  return `${prop.formula(alias)} as ${aliased}`;
766
941
  })
767
- .filter(field => !this._fields.includes(field))
768
- .forEach(field => this.addSelect(field));
942
+ .filter(field => !this._fields.some(f => {
943
+ if (f instanceof core_1.RawQueryFragment) {
944
+ return f.sql === field && f.params.length === 0;
945
+ }
946
+ return f === field;
947
+ }))
948
+ .forEach(field => this._fields.push((0, core_1.raw)(field)));
769
949
  }
950
+ this.processPopulateWhere();
770
951
  core_1.QueryHelper.processObjectParams(this._data);
771
952
  core_1.QueryHelper.processObjectParams(this._cond);
772
953
  core_1.QueryHelper.processObjectParams(this._having);
773
- // automatically enable paginate flag when we detect to-many joins
774
- if (!this.flags.has(core_1.QueryFlag.DISABLE_PAGINATE) && this.hasToManyJoins()) {
954
+ // automatically enable paginate flag when we detect to-many joins, but only if there is no `group by` clause
955
+ if (!this.flags.has(core_1.QueryFlag.DISABLE_PAGINATE) && this._groupBy.length === 0 && this.hasToManyJoins()) {
775
956
  this.flags.add(core_1.QueryFlag.PAGINATE);
776
957
  }
777
958
  if (meta && this.flags.has(core_1.QueryFlag.PAGINATE) && (this._limit > 0 || this._offset > 0)) {
@@ -782,17 +963,54 @@ class QueryBuilder {
782
963
  }
783
964
  this.finalized = true;
784
965
  }
966
+ processPopulateWhere() {
967
+ if (this._populateWhere == null || this._populateWhere === core_1.PopulateHint.ALL) {
968
+ return;
969
+ }
970
+ const joins = Object.values(this._joins);
971
+ joins.forEach(join => {
972
+ join.cond_ = join.cond;
973
+ join.cond = {};
974
+ });
975
+ const replaceOnConditions = (cond) => {
976
+ Object.keys(cond).forEach(k => {
977
+ if (core_1.Utils.isOperator(k)) {
978
+ if (Array.isArray(cond[k])) {
979
+ return cond[k].forEach((c) => replaceOnConditions(c));
980
+ }
981
+ return replaceOnConditions(cond[k]);
982
+ }
983
+ const [a] = this.helper.splitField(k);
984
+ const join = joins.find(j => j.alias === a);
985
+ if (join) {
986
+ join.cond = { ...join.cond, [k]: cond[k] };
987
+ }
988
+ });
989
+ };
990
+ if (this._populateWhere === core_1.PopulateHint.INFER) {
991
+ replaceOnConditions(this._cond);
992
+ }
993
+ else if (typeof this._populateWhere === 'object') {
994
+ const cond = CriteriaNodeFactory_1.CriteriaNodeFactory
995
+ .createNode(this.metadata, this.mainAlias.entityName, this._populateWhere)
996
+ .process(this);
997
+ replaceOnConditions(cond);
998
+ }
999
+ }
785
1000
  hasToManyJoins() {
786
1001
  return Object.values(this._joins).some(join => {
787
- return [core_1.ReferenceType.ONE_TO_MANY, core_1.ReferenceType.MANY_TO_MANY].includes(join.prop.reference);
1002
+ return [core_1.ReferenceKind.ONE_TO_MANY, core_1.ReferenceKind.MANY_TO_MANY].includes(join.prop.kind);
788
1003
  });
789
1004
  }
790
1005
  wrapPaginateSubQuery(meta) {
791
1006
  const pks = this.prepareFields(meta.primaryKeys, 'sub-query');
792
1007
  const subQuery = this.clone().select(pks).groupBy(pks).limit(this._limit);
1008
+ // revert the on conditions added via populateWhere, we want to apply those only once
1009
+ Object.values(subQuery._joins).forEach(join => join.cond = join.cond_ ?? {});
793
1010
  if (this._offset) {
794
1011
  subQuery.offset(this._offset);
795
1012
  }
1013
+ const addToSelect = [];
796
1014
  if (this._orderBy.length > 0) {
797
1015
  const orderBy = [];
798
1016
  for (const orderMap of this._orderBy) {
@@ -800,28 +1018,38 @@ class QueryBuilder {
800
1018
  const [a, f] = this.helper.splitField(field);
801
1019
  const prop = this.helper.getProperty(f, a);
802
1020
  const type = this.platform.castColumn(prop);
803
- orderBy.push({
804
- [`min(${this.ref(this.helper.mapper(field, this.type, undefined, null))}${type})`]: direction,
805
- });
1021
+ const fieldName = this.helper.mapper(field, this.type, undefined, null);
1022
+ if (!prop?.persist && !prop?.formula) {
1023
+ addToSelect.push(fieldName);
1024
+ }
1025
+ orderBy.push({ [(0, core_1.raw)(`min(${this.knex.ref(fieldName)}${type})`)]: direction });
806
1026
  }
807
1027
  }
808
1028
  subQuery.orderBy(orderBy);
809
1029
  }
810
1030
  subQuery.finalized = true;
811
1031
  const knexQuery = subQuery.as(this.mainAlias.aliasName).clearSelect().select(pks);
1032
+ if (addToSelect.length > 0) {
1033
+ addToSelect.forEach(prop => {
1034
+ const field = this._fields.find(field => {
1035
+ if (typeof field === 'object' && field && '__as' in field) {
1036
+ return field.__as === prop;
1037
+ }
1038
+ // not perfect, but should work most of the time, ideally we should check only the alias (`... as alias`)
1039
+ return field.toString().includes(prop);
1040
+ });
1041
+ if (field) {
1042
+ knexQuery.select(field);
1043
+ }
1044
+ });
1045
+ }
812
1046
  // multiple sub-queries are needed to get around mysql limitations with order by + limit + where in + group by (o.O)
813
1047
  // https://stackoverflow.com/questions/17892762/mysql-this-version-of-mysql-doesnt-yet-support-limit-in-all-any-some-subqu
814
1048
  const subSubQuery = this.getKnex().select(pks).from(knexQuery);
1049
+ subSubQuery.__raw = true; // tag it as there is now way to check via `instanceof`
815
1050
  this._limit = undefined;
816
1051
  this._offset = undefined;
817
- const cond = this._cond;
818
1052
  this.select(this._fields).where({ [core_1.Utils.getPrimaryKeyHash(meta.primaryKeys)]: { $in: subSubQuery } });
819
- if (this._populateWhere === core_1.PopulateHint.INFER) {
820
- this.andWhere(cond);
821
- }
822
- else if (typeof this._populateWhere === 'object') {
823
- this.andWhere(this._populateWhere);
824
- }
825
1053
  }
826
1054
  wrapModifySubQuery(meta) {
827
1055
  const subQuery = this.clone();
@@ -835,17 +1063,6 @@ class QueryBuilder {
835
1063
  [core_1.Utils.getPrimaryKeyHash(meta.primaryKeys)]: { $in: subSubQuery },
836
1064
  });
837
1065
  }
838
- autoJoinPivotTable(field) {
839
- const pivotMeta = this.metadata.find(field);
840
- const owner = pivotMeta.relations[0];
841
- const inverse = pivotMeta.relations[1];
842
- const prop = this._cond[pivotMeta.name + '.' + owner.name] || this._orderBy[pivotMeta.name + '.' + owner.name] ? inverse : owner;
843
- const pivotAlias = this.getNextAlias(pivotMeta.name);
844
- this._joins[field] = this.helper.joinPivotTable(field, prop, this.mainAlias.aliasName, pivotAlias, 'leftJoin');
845
- core_1.Utils.renameKey(this._cond, `${field}.${owner.name}`, core_1.Utils.getPrimaryKeyHash(owner.fieldNames.map(fieldName => `${pivotAlias}.${fieldName}`)));
846
- core_1.Utils.renameKey(this._cond, `${field}.${inverse.name}`, core_1.Utils.getPrimaryKeyHash(inverse.fieldNames.map(fieldName => `${pivotAlias}.${fieldName}`)));
847
- this._populateMap[field] = this._joins[field].alias;
848
- }
849
1066
  getSchema(alias) {
850
1067
  const { metadata } = alias;
851
1068
  const metaSchema = metadata?.schema && metadata.schema !== '*' ? metadata.schema : undefined;
@@ -865,11 +1082,11 @@ class QueryBuilder {
865
1082
  fromSubQuery(target, aliasName) {
866
1083
  const subQuery = target.getKnexQuery();
867
1084
  const { entityName } = target.mainAlias;
868
- aliasName ?? (aliasName = this.getNextAlias(entityName));
1085
+ aliasName ??= this.getNextAlias(entityName);
869
1086
  this.createMainAlias(entityName, aliasName, subQuery);
870
1087
  }
871
1088
  fromEntityName(entityName, aliasName) {
872
- aliasName ?? (aliasName = this._mainAlias?.aliasName ?? this.getNextAlias(entityName));
1089
+ aliasName ??= this._mainAlias?.aliasName ?? this.getNextAlias(entityName);
873
1090
  this.createMainAlias(entityName, aliasName);
874
1091
  }
875
1092
  createQueryBuilderHelper() {
@@ -897,6 +1114,9 @@ class QueryBuilder {
897
1114
  if (this._data) {
898
1115
  object.data = this._data;
899
1116
  }
1117
+ if (this._schema) {
1118
+ object.schema = this._schema;
1119
+ }
900
1120
  if (!core_1.Utils.isEmpty(this._cond)) {
901
1121
  object.where = this._cond;
902
1122
  }