@mikro-orm/knex 6.0.0-dev.2 → 6.0.0-dev.20

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 (54) hide show
  1. package/{dist/AbstractSqlConnection.d.ts → AbstractSqlConnection.d.ts} +0 -0
  2. package/{dist/AbstractSqlConnection.js → AbstractSqlConnection.js} +5 -0
  3. package/AbstractSqlDriver.d.ts +66 -0
  4. package/{dist/AbstractSqlDriver.js → AbstractSqlDriver.js} +230 -71
  5. package/{dist/AbstractSqlPlatform.d.ts → AbstractSqlPlatform.d.ts} +7 -7
  6. package/{dist/AbstractSqlPlatform.js → AbstractSqlPlatform.js} +12 -14
  7. package/{dist/MonkeyPatchable.d.ts → MonkeyPatchable.d.ts} +0 -0
  8. package/{dist/MonkeyPatchable.js → MonkeyPatchable.js} +0 -0
  9. package/{dist/README.md → README.md} +2 -2
  10. package/{dist/SqlEntityManager.d.ts → SqlEntityManager.d.ts} +5 -5
  11. package/{dist/SqlEntityManager.js → SqlEntityManager.js} +1 -2
  12. package/{dist/SqlEntityRepository.d.ts → SqlEntityRepository.d.ts} +3 -3
  13. package/{dist/SqlEntityRepository.js → SqlEntityRepository.js} +1 -1
  14. package/{dist/index.d.ts → index.d.ts} +1 -0
  15. package/{dist/index.js → index.js} +6 -1
  16. package/index.mjs +192 -0
  17. package/package.json +18 -47
  18. package/{dist/query → query}/ArrayCriteriaNode.d.ts +0 -0
  19. package/{dist/query → query}/ArrayCriteriaNode.js +0 -0
  20. package/{dist/query → query}/CriteriaNode.d.ts +0 -0
  21. package/{dist/query → query}/CriteriaNode.js +7 -4
  22. package/{dist/query → query}/CriteriaNodeFactory.d.ts +0 -0
  23. package/{dist/query → query}/CriteriaNodeFactory.js +0 -0
  24. package/{dist/query → query}/ObjectCriteriaNode.d.ts +0 -0
  25. package/{dist/query → query}/ObjectCriteriaNode.js +14 -1
  26. package/{dist/query → query}/QueryBuilder.d.ts +65 -24
  27. package/{dist/query → query}/QueryBuilder.js +318 -84
  28. package/{dist/query → query}/QueryBuilderHelper.d.ts +19 -11
  29. package/{dist/query → query}/QueryBuilderHelper.js +224 -113
  30. package/{dist/query → query}/ScalarCriteriaNode.d.ts +0 -0
  31. package/{dist/query → query}/ScalarCriteriaNode.js +0 -0
  32. package/{dist/query → query}/enums.d.ts +0 -0
  33. package/{dist/query → query}/enums.js +0 -0
  34. package/{dist/query → query}/index.d.ts +0 -0
  35. package/{dist/query → query}/index.js +5 -1
  36. package/{dist/schema → schema}/DatabaseSchema.d.ts +1 -1
  37. package/{dist/schema → schema}/DatabaseSchema.js +8 -16
  38. package/{dist/schema → schema}/DatabaseTable.d.ts +8 -8
  39. package/{dist/schema → schema}/DatabaseTable.js +2 -1
  40. package/{dist/schema → schema}/SchemaComparator.d.ts +7 -5
  41. package/{dist/schema → schema}/SchemaComparator.js +60 -10
  42. package/{dist/schema → schema}/SchemaHelper.d.ts +15 -10
  43. package/{dist/schema → schema}/SchemaHelper.js +46 -8
  44. package/{dist/schema/SchemaGenerator.d.ts → schema/SqlSchemaGenerator.d.ts} +12 -12
  45. package/{dist/schema/SchemaGenerator.js → schema/SqlSchemaGenerator.js} +85 -34
  46. package/{dist/schema → schema}/index.d.ts +1 -1
  47. package/{dist/schema → schema}/index.js +6 -2
  48. package/{dist/typings.d.ts → typings.d.ts} +16 -12
  49. package/{dist/typings.js → typings.js} +0 -0
  50. package/CHANGELOG.md +0 -661
  51. package/dist/AbstractSqlDriver.d.ts +0 -55
  52. package/dist/LICENSE +0 -21
  53. package/dist/index.mjs +0 -26
  54. package/dist/package.json +0 -99
@@ -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,11 +26,21 @@ const CriteriaNodeFactory_1 = require("./CriteriaNodeFactory");
25
26
  * ```
26
27
  */
27
28
  class QueryBuilder {
29
+ get mainAlias() {
30
+ this.ensureFromClause();
31
+ return this._mainAlias;
32
+ }
33
+ get alias() {
34
+ return this.mainAlias.aliasName;
35
+ }
36
+ get helper() {
37
+ this.ensureFromClause();
38
+ return this._helper;
39
+ }
28
40
  /**
29
41
  * @internal
30
42
  */
31
43
  constructor(entityName, metadata, driver, context, alias, connectionType, em) {
32
- this.entityName = entityName;
33
44
  this.metadata = metadata;
34
45
  this.driver = driver;
35
46
  this.context = context;
@@ -43,23 +54,25 @@ class QueryBuilder {
43
54
  this.flags = new Set([core_1.QueryFlag.CONVERT_CUSTOM_TYPES]);
44
55
  this.finalized = false;
45
56
  this._joins = {};
46
- this._aliasMap = {};
57
+ this._explicitAlias = false;
47
58
  this._cond = {};
48
59
  this._orderBy = [];
49
60
  this._groupBy = [];
50
61
  this._having = {};
51
62
  this._joinedProps = new Map();
52
63
  this.subQueries = {};
64
+ this._aliases = {};
53
65
  this.platform = this.driver.getPlatform();
54
66
  this.knex = this.driver.getConnection(this.connectionType).getKnex();
55
67
  if (alias) {
56
68
  this.aliasCounter++;
69
+ this._explicitAlias = true;
57
70
  }
58
- this.alias = alias ?? this.getNextAlias(this.entityName);
59
- this._aliasMap[this.alias] = this.entityName;
60
- this.helper = new QueryBuilderHelper_1.QueryBuilderHelper(this.entityName, this.alias, this._aliasMap, this.subQueries, this.metadata, this.knex, this.platform);
71
+ // @ts-expect-error union type does not match the overloaded method signature
72
+ this.from(entityName, alias);
61
73
  }
62
74
  select(fields, distinct = false) {
75
+ this.ensureNotFinalized();
63
76
  this._fields = core_1.Utils.asArray(fields);
64
77
  if (distinct) {
65
78
  this.flags.add(core_1.QueryFlag.DISTINCT);
@@ -67,11 +80,22 @@ class QueryBuilder {
67
80
  return this.init(enums_1.QueryType.SELECT);
68
81
  }
69
82
  addSelect(fields) {
83
+ this.ensureNotFinalized();
70
84
  if (this.type && this.type !== enums_1.QueryType.SELECT) {
71
85
  return this;
72
86
  }
73
87
  return this.select([...core_1.Utils.asArray(this._fields), ...core_1.Utils.asArray(fields)]);
74
88
  }
89
+ distinct() {
90
+ this.ensureNotFinalized();
91
+ return this.setFlag(core_1.QueryFlag.DISTINCT);
92
+ }
93
+ /** postgres only */
94
+ distinctOn(fields) {
95
+ this.ensureNotFinalized();
96
+ this._distinctOn = core_1.Utils.asArray(fields);
97
+ return this;
98
+ }
75
99
  insert(data) {
76
100
  return this.init(enums_1.QueryType.INSERT, data);
77
101
  }
@@ -85,7 +109,15 @@ class QueryBuilder {
85
109
  return this.init(enums_1.QueryType.TRUNCATE);
86
110
  }
87
111
  count(field, distinct = false) {
88
- this._fields = [...(field ? core_1.Utils.asArray(field) : this.metadata.find(this.entityName).primaryKeys)];
112
+ if (field) {
113
+ this._fields = core_1.Utils.asArray(field);
114
+ }
115
+ else if (this.hasToManyJoins()) {
116
+ this._fields = this.mainAlias.metadata.primaryKeys;
117
+ }
118
+ else {
119
+ this._fields = [this.raw('*')];
120
+ }
89
121
  if (distinct) {
90
122
  this.flags.add(core_1.QueryFlag.DISTINCT);
91
123
  }
@@ -99,6 +131,9 @@ class QueryBuilder {
99
131
  return this.join(field, alias, cond, 'leftJoin');
100
132
  }
101
133
  joinAndSelect(field, alias, cond = {}, type = 'innerJoin', path) {
134
+ if (!this.type) {
135
+ this.select('*');
136
+ }
102
137
  const prop = this.joinReference(field, alias, cond, type, path);
103
138
  this.addSelect(this.getFieldsForJoinedLoad(prop, alias));
104
139
  const [fromAlias] = this.helper.splitField(field);
@@ -124,24 +159,34 @@ class QueryBuilder {
124
159
  return fields;
125
160
  }
126
161
  withSubQuery(subQuery, alias) {
162
+ this.ensureNotFinalized();
127
163
  this.subQueries[alias] = subQuery.toString();
128
164
  return this;
129
165
  }
130
166
  where(cond, params, operator) {
167
+ this.ensureNotFinalized();
131
168
  if (core_1.Utils.isString(cond)) {
132
169
  cond = { [`(${cond})`]: core_1.Utils.asArray(params) };
133
170
  operator = operator || '$and';
134
171
  }
135
172
  else {
136
- cond = core_1.QueryHelper.processWhere(cond, this.entityName, this.metadata, this.platform, this.flags.has(core_1.QueryFlag.CONVERT_CUSTOM_TYPES));
173
+ cond = core_1.QueryHelper.processWhere({
174
+ where: cond,
175
+ entityName: this.mainAlias.entityName,
176
+ metadata: this.metadata,
177
+ platform: this.platform,
178
+ aliasMap: this.getAliasMap(),
179
+ aliased: !this.type || [enums_1.QueryType.SELECT, enums_1.QueryType.COUNT].includes(this.type),
180
+ convertCustomTypes: this.flags.has(core_1.QueryFlag.CONVERT_CUSTOM_TYPES),
181
+ });
137
182
  }
138
183
  const op = operator || params;
139
184
  const topLevel = !op || !core_1.Utils.hasObjectKeys(this._cond);
140
- const criteriaNode = CriteriaNodeFactory_1.CriteriaNodeFactory.createNode(this.metadata, this.entityName, cond);
185
+ const criteriaNode = CriteriaNodeFactory_1.CriteriaNodeFactory.createNode(this.metadata, this.mainAlias.entityName, cond);
141
186
  if ([enums_1.QueryType.UPDATE, enums_1.QueryType.DELETE].includes(this.type) && criteriaNode.willAutoJoin(this)) {
142
187
  // use sub-query to support joining
143
188
  this.setFlag(this.type === enums_1.QueryType.UPDATE ? core_1.QueryFlag.UPDATE_SUB_QUERY : core_1.QueryFlag.DELETE_SUB_QUERY);
144
- this.select(this.metadata.find(this.entityName).primaryKeys, true);
189
+ this.select(this.mainAlias.metadata.primaryKeys, true);
145
190
  }
146
191
  if (topLevel) {
147
192
  this._cond = criteriaNode.process(this);
@@ -166,27 +211,39 @@ class QueryBuilder {
166
211
  return this.where(cond, params, '$or');
167
212
  }
168
213
  orderBy(orderBy) {
214
+ this.ensureNotFinalized();
169
215
  this._orderBy = [];
170
216
  core_1.Utils.asArray(orderBy).forEach(o => {
171
- const processed = core_1.QueryHelper.processWhere(o, this.entityName, this.metadata, this.platform, false);
172
- this._orderBy.push(CriteriaNodeFactory_1.CriteriaNodeFactory.createNode(this.metadata, this.entityName, processed).process(this));
217
+ const processed = core_1.QueryHelper.processWhere({
218
+ where: o,
219
+ entityName: this.mainAlias.entityName,
220
+ metadata: this.metadata,
221
+ platform: this.platform,
222
+ aliasMap: this.getAliasMap(),
223
+ aliased: !this.type || [enums_1.QueryType.SELECT, enums_1.QueryType.COUNT].includes(this.type),
224
+ convertCustomTypes: false,
225
+ });
226
+ this._orderBy.push(CriteriaNodeFactory_1.CriteriaNodeFactory.createNode(this.metadata, this.mainAlias.entityName, processed).process(this));
173
227
  });
174
228
  return this;
175
229
  }
176
230
  groupBy(fields) {
231
+ this.ensureNotFinalized();
177
232
  this._groupBy = core_1.Utils.asArray(fields);
178
233
  return this;
179
234
  }
180
235
  having(cond = {}, params) {
236
+ this.ensureNotFinalized();
181
237
  if (core_1.Utils.isString(cond)) {
182
238
  cond = { [`(${cond})`]: core_1.Utils.asArray(params) };
183
239
  }
184
- this._having = CriteriaNodeFactory_1.CriteriaNodeFactory.createNode(this.metadata, this.entityName, cond).process(this);
240
+ this._having = CriteriaNodeFactory_1.CriteriaNodeFactory.createNode(this.metadata, this.mainAlias.entityName, cond).process(this);
185
241
  return this;
186
242
  }
187
243
  onConflict(fields = []) {
244
+ this.ensureNotFinalized();
188
245
  this._onConflict = this._onConflict || [];
189
- this._onConflict.push({ fields: core_1.Utils.asArray(fields) });
246
+ this._onConflict.push({ fields: core_1.Utils.asArray(fields).map(f => f.toString()) });
190
247
  return this;
191
248
  }
192
249
  ignore() {
@@ -200,14 +257,19 @@ class QueryBuilder {
200
257
  if (!this._onConflict) {
201
258
  throw new Error('You need to call `qb.onConflict()` first to use `qb.merge()`');
202
259
  }
260
+ if (Array.isArray(data) && data.length === 0) {
261
+ return this.ignore();
262
+ }
203
263
  this._onConflict[this._onConflict.length - 1].merge = data;
204
264
  return this;
205
265
  }
206
266
  /**
207
267
  * @internal
208
268
  */
209
- populate(populate) {
269
+ populate(populate, populateWhere) {
270
+ this.ensureNotFinalized();
210
271
  this._populate = populate;
272
+ this._populateWhere = populateWhere;
211
273
  return this;
212
274
  }
213
275
  /**
@@ -222,6 +284,7 @@ class QueryBuilder {
222
284
  return raw;
223
285
  }
224
286
  limit(limit, offset = 0) {
287
+ this.ensureNotFinalized();
225
288
  this._limit = limit;
226
289
  if (offset) {
227
290
  this.offset(offset);
@@ -229,14 +292,17 @@ class QueryBuilder {
229
292
  return this;
230
293
  }
231
294
  offset(offset) {
295
+ this.ensureNotFinalized();
232
296
  this._offset = offset;
233
297
  return this;
234
298
  }
235
299
  withSchema(schema) {
300
+ this.ensureNotFinalized();
236
301
  this._schema = schema;
237
302
  return this;
238
303
  }
239
304
  setLockMode(mode, tables) {
305
+ this.ensureNotFinalized();
240
306
  if (mode != null && mode !== core_1.LockMode.OPTIMISTIC && !this.context) {
241
307
  throw core_1.ValidationError.transactionRequired();
242
308
  }
@@ -245,18 +311,22 @@ class QueryBuilder {
245
311
  return this;
246
312
  }
247
313
  setFlushMode(flushMode) {
314
+ this.ensureNotFinalized();
248
315
  this.flushMode = flushMode;
249
316
  return this;
250
317
  }
251
318
  setFlag(flag) {
319
+ this.ensureNotFinalized();
252
320
  this.flags.add(flag);
253
321
  return this;
254
322
  }
255
323
  unsetFlag(flag) {
324
+ this.ensureNotFinalized();
256
325
  this.flags.delete(flag);
257
326
  return this;
258
327
  }
259
328
  cache(config = true) {
329
+ this.ensureNotFinalized();
260
330
  this._cache = config;
261
331
  return this;
262
332
  }
@@ -264,31 +334,46 @@ class QueryBuilder {
264
334
  * Adds index hint to the FROM clause.
265
335
  */
266
336
  indexHint(sql) {
337
+ this.ensureNotFinalized();
267
338
  this._indexHint = sql;
268
339
  return this;
269
340
  }
341
+ from(target, aliasName) {
342
+ this.ensureNotFinalized();
343
+ if (target instanceof QueryBuilder) {
344
+ this.fromSubQuery(target, aliasName);
345
+ }
346
+ else {
347
+ const entityName = core_1.Utils.className(target);
348
+ if (aliasName && this._mainAlias && entityName !== this._mainAlias.aliasName) {
349
+ throw new Error(`Cannot override the alias to '${aliasName}' since a query already contains references to '${this._mainAlias.aliasName}'`);
350
+ }
351
+ this.fromEntityName(entityName, aliasName);
352
+ }
353
+ return this;
354
+ }
270
355
  getKnexQuery() {
271
356
  this.finalize();
272
357
  const qb = this.getQueryBase();
273
- core_1.Utils.runIfNotEmpty(() => this.helper.appendQueryCondition(this.type, this._cond, qb), this._cond && !this._onConflict);
358
+ core_1.Utils.runIfNotEmpty(() => this.helper.appendQueryCondition(this.type ?? enums_1.QueryType.SELECT, this._cond, qb), this._cond && !this._onConflict);
274
359
  core_1.Utils.runIfNotEmpty(() => qb.groupBy(this.prepareFields(this._groupBy, 'groupBy')), this._groupBy);
275
- core_1.Utils.runIfNotEmpty(() => this.helper.appendQueryCondition(this.type, this._having, qb, undefined, 'having'), this._having);
360
+ core_1.Utils.runIfNotEmpty(() => this.helper.appendQueryCondition(this.type ?? enums_1.QueryType.SELECT, this._having, qb, undefined, 'having'), this._having);
276
361
  core_1.Utils.runIfNotEmpty(() => {
277
- const queryOrder = this.helper.getQueryOrder(this.type, this._orderBy, this._populateMap);
362
+ const queryOrder = this.helper.getQueryOrder(this.type ?? enums_1.QueryType.SELECT, this._orderBy, this._populateMap);
278
363
  if (queryOrder) {
279
364
  return qb.orderByRaw(queryOrder);
280
365
  }
281
366
  }, this._orderBy);
282
367
  core_1.Utils.runIfNotEmpty(() => qb.limit(this._limit), this._limit != null);
283
368
  core_1.Utils.runIfNotEmpty(() => qb.offset(this._offset), this._offset);
284
- core_1.Utils.runIfNotEmpty(() => this.helper.appendOnConflictClause(this.type, this._onConflict, qb), this._onConflict);
369
+ core_1.Utils.runIfNotEmpty(() => this.helper.appendOnConflictClause(this.type ?? enums_1.QueryType.SELECT, this._onConflict, qb), this._onConflict);
285
370
  if (this.type === enums_1.QueryType.TRUNCATE && this.platform.usesCascadeStatement()) {
286
371
  return this.knex.raw(qb.toSQL().toNative().sql + ' cascade');
287
372
  }
288
373
  if (this.lockMode) {
289
374
  this.helper.getLockSQL(qb, this.lockMode, this.lockTables);
290
375
  }
291
- this.helper.finalize(this.type, qb, this.metadata.find(this.entityName));
376
+ this.helper.finalize(this.type ?? enums_1.QueryType.SELECT, qb, this.mainAlias.metadata);
292
377
  return qb;
293
378
  }
294
379
  /**
@@ -310,9 +395,12 @@ class QueryBuilder {
310
395
  const query = this.getKnexQuery().toSQL();
311
396
  return this.platform.formatQuery(query.sql, query.bindings);
312
397
  }
398
+ /**
399
+ * @internal
400
+ */
313
401
  getAliasForJoinPath(path) {
314
- if (!path || path === this.entityName) {
315
- return this.alias;
402
+ if (!path || path === this.mainAlias.entityName) {
403
+ return this.mainAlias.aliasName;
316
404
  }
317
405
  const join = Object.values(this._joins).find(j => j.path === path);
318
406
  if (path.endsWith('[pivot]') && join) {
@@ -320,25 +408,34 @@ class QueryBuilder {
320
408
  }
321
409
  return join?.inverseAlias || join?.alias;
322
410
  }
411
+ /**
412
+ * @internal
413
+ */
323
414
  getNextAlias(entityName = 'e') {
324
415
  return this.driver.config.getNamingStrategy().aliasName(entityName, this.aliasCounter++);
325
416
  }
417
+ /**
418
+ * @internal
419
+ */
420
+ getAliasMap() {
421
+ return Object.fromEntries(Object.entries(this._aliases).map(([key, value]) => [key, value.entityName]));
422
+ }
326
423
  /**
327
424
  * Executes this QB and returns the raw results, mapped to the property names (unless disabled via last parameter).
328
425
  * Use `method` to specify what kind of result you want to get (array/single/meta).
329
426
  */
330
427
  async execute(method = 'all', mapResults = true) {
331
- if (!this.connectionType && method !== 'run' && [enums_1.QueryType.INSERT, enums_1.QueryType.UPDATE, enums_1.QueryType.DELETE, enums_1.QueryType.TRUNCATE].includes(this.type)) {
428
+ 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)) {
332
429
  this.connectionType = 'write';
333
430
  }
334
431
  const query = this.getKnexQuery().toSQL();
335
- const cached = await this.em?.tryCache(this.entityName, this._cache, ['qb.execute', query.sql, query.bindings, method]);
432
+ const cached = await this.em?.tryCache(this.mainAlias.entityName, this._cache, ['qb.execute', query.sql, query.bindings, method]);
336
433
  if (cached?.data) {
337
434
  return cached.data;
338
435
  }
339
436
  const type = this.connectionType || (method === 'run' ? 'write' : 'read');
340
437
  const res = await this.driver.getConnection(type).execute(query.sql, query.bindings, method, this.context);
341
- const meta = this.metadata.find(this.entityName);
438
+ const meta = this.mainAlias.metadata;
342
439
  if (!mapResults || !meta) {
343
440
  await this.em?.storeCache(this._cache, cached, res);
344
441
  return res;
@@ -363,12 +460,12 @@ class QueryBuilder {
363
460
  * Executes the query, returning array of results
364
461
  */
365
462
  async getResultList() {
366
- await this.em.tryFlush(this.entityName, { flushMode: this.flushMode });
463
+ await this.em.tryFlush(this.mainAlias.entityName, { flushMode: this.flushMode });
367
464
  let res = await this.execute('all', true);
368
465
  if (this._joinedProps.size > 0) {
369
- res = this.driver.mergeJoinedResult(res, this.metadata.find(this.entityName));
466
+ res = this.driver.mergeJoinedResult(res, this.mainAlias.metadata);
370
467
  }
371
- return res.map(r => this.em.map(this.entityName, r, { schema: this._schema }));
468
+ return res.map(r => this.em.map(this.mainAlias.entityName, r, { schema: this._schema }));
372
469
  }
373
470
  /**
374
471
  * Executes the query, returning the first result or null
@@ -380,35 +477,41 @@ class QueryBuilder {
380
477
  /**
381
478
  * Executes count query (without offset and limit), returning total count of results
382
479
  */
383
- async getCount(field, distinct = false) {
384
- const qb = this.clone();
385
- qb.count(field, distinct).limit(undefined).offset(undefined);
386
- const res = await qb.execute('get', false);
480
+ async getCount(field, distinct) {
481
+ let res;
482
+ if (this.type === enums_1.QueryType.COUNT) {
483
+ res = await this.execute('get', false);
484
+ }
485
+ else {
486
+ const qb = this.clone();
487
+ qb.count(field, distinct ?? qb.hasToManyJoins()).limit(undefined).offset(undefined).orderBy([]);
488
+ res = await qb.execute('get', false);
489
+ }
387
490
  return res ? +res.count : 0;
388
491
  }
492
+ /**
493
+ * Executes the query, returning both array of results and total count query (without offset and limit).
494
+ */
495
+ async getResultAndCount() {
496
+ return Promise.all([
497
+ this.getResultList(),
498
+ this.getCount(),
499
+ ]);
500
+ }
389
501
  /**
390
502
  * Provides promise-like interface so we can await the QB instance.
391
503
  */
392
504
  then(onfulfilled, onrejected) {
393
- return this.getInnerPromise().then(onfulfilled, onrejected);
394
- }
395
- getInnerPromise() {
396
- if (!this.innerPromise) {
397
- this.innerPromise = (async () => {
398
- switch (this.type) {
399
- case enums_1.QueryType.INSERT:
400
- case enums_1.QueryType.UPDATE:
401
- case enums_1.QueryType.DELETE:
402
- case enums_1.QueryType.TRUNCATE:
403
- return this.execute('run');
404
- case enums_1.QueryType.SELECT:
405
- return this.getResultList();
406
- case enums_1.QueryType.COUNT:
407
- return this.getCount();
408
- }
409
- })();
505
+ switch (this.type ?? enums_1.QueryType.SELECT) {
506
+ case enums_1.QueryType.INSERT:
507
+ case enums_1.QueryType.UPDATE:
508
+ case enums_1.QueryType.DELETE:
509
+ case enums_1.QueryType.TRUNCATE:
510
+ return this.execute('run').then(onfulfilled, onrejected);
511
+ case enums_1.QueryType.COUNT:
512
+ return this.getCount().then(onfulfilled, onrejected);
513
+ case enums_1.QueryType.SELECT: return this.getResultList().then(onfulfilled, onrejected);
410
514
  }
411
- return this.innerPromise;
412
515
  }
413
516
  /**
414
517
  * Returns knex instance with sub-query aliased with given alias.
@@ -425,11 +528,11 @@ class QueryBuilder {
425
528
  return qb.as(alias);
426
529
  }
427
530
  clone() {
428
- const qb = new QueryBuilder(this.entityName, this.metadata, this.driver, this.context, this.alias, this.connectionType, this.em);
531
+ const qb = new QueryBuilder(this.mainAlias.entityName, this.metadata, this.driver, this.context, this.mainAlias.aliasName, this.connectionType, this.em);
429
532
  Object.assign(qb, this);
430
533
  // clone array/object properties
431
534
  const properties = [
432
- 'flags', '_populate', '_populateMap', '_joins', '_joinedProps', '_aliasMap', '_cond', '_data', '_orderBy',
535
+ 'flags', '_populate', '_populateWhere', '_populateMap', '_joins', '_joinedProps', '_cond', '_data', '_orderBy',
433
536
  '_schema', '_indexHint', '_cache', 'subQueries', 'lockMode', 'lockTables',
434
537
  ];
435
538
  properties.forEach(prop => qb[prop] = core_1.Utils.copy(this[prop]));
@@ -437,27 +540,45 @@ class QueryBuilder {
437
540
  if (this._fields) {
438
541
  qb._fields = [...this._fields];
439
542
  }
543
+ qb._aliases = { ...this._aliases };
440
544
  qb.finalized = false;
441
545
  return qb;
442
546
  }
443
547
  getKnex() {
444
- const tableName = this.helper.getTableName(this.entityName) + (this.finalized && this.helper.isTableNameAliasRequired(this.type) ? ` as ${this.alias}` : '');
445
- const qb = this.knex(tableName);
548
+ const qb = this.knex.queryBuilder();
549
+ const { subQuery, aliasName, entityName } = this.mainAlias;
550
+ const ref = subQuery ? subQuery : this.knex.ref(this.helper.getTableName(entityName));
551
+ if (this.finalized && (this._explicitAlias || this.helper.isTableNameAliasRequired(this.type))) {
552
+ ref.as(aliasName);
553
+ }
554
+ const schema = this.getSchema(this.mainAlias);
555
+ if (schema) {
556
+ ref.withSchema(schema);
557
+ }
558
+ qb.from(ref);
446
559
  if (this.context) {
447
560
  qb.transacting(this.context);
448
561
  }
449
562
  return qb;
450
563
  }
451
564
  joinReference(field, alias, cond, type, path) {
565
+ this.ensureNotFinalized();
452
566
  const [fromAlias, fromField] = this.helper.splitField(field);
453
- const entityName = this._aliasMap[fromAlias];
567
+ const entityName = this._aliases[fromAlias]?.entityName;
454
568
  const meta = this.metadata.get(entityName);
455
569
  const prop = meta.properties[fromField];
456
570
  if (!prop) {
457
571
  throw new Error(`Trying to join ${field}, but ${fromField} is not a defined relation on ${meta.className}`);
458
572
  }
459
- this._aliasMap[alias] = prop.type;
460
- cond = core_1.QueryHelper.processWhere(cond, this.entityName, this.metadata, this.platform);
573
+ this.createAlias(prop.type, alias);
574
+ cond = core_1.QueryHelper.processWhere({
575
+ where: cond,
576
+ entityName: this.mainAlias.entityName,
577
+ metadata: this.metadata,
578
+ platform: this.platform,
579
+ aliasMap: this.getAliasMap(),
580
+ aliased: !this.type || [enums_1.QueryType.SELECT, enums_1.QueryType.COUNT].includes(this.type),
581
+ });
461
582
  let aliasedName = `${fromAlias}.${prop.name}#${alias}`;
462
583
  path ?? (path = `${(Object.values(this._joins).find(j => j.alias === fromAlias)?.path ?? entityName)}.${prop.name}`);
463
584
  if (prop.reference === core_1.ReferenceType.ONE_TO_MANY) {
@@ -467,12 +588,12 @@ class QueryBuilder {
467
588
  let pivotAlias = alias;
468
589
  if (type !== 'pivotJoin') {
469
590
  const oldPivotAlias = this.getAliasForJoinPath(path + '[pivot]');
470
- pivotAlias = oldPivotAlias ?? this.getNextAlias(prop.pivotTable);
591
+ pivotAlias = oldPivotAlias ?? this.getNextAlias(prop.pivotEntity);
471
592
  aliasedName = `${fromAlias}.${prop.name}#${pivotAlias}`;
472
593
  }
473
594
  const joins = this.helper.joinManyToManyReference(prop, fromAlias, alias, pivotAlias, type, cond, path);
474
595
  Object.assign(this._joins, joins);
475
- this._aliasMap[pivotAlias] = prop.pivotTable;
596
+ this.createAlias(prop.pivotEntity, pivotAlias);
476
597
  }
477
598
  else if (prop.reference === core_1.ReferenceType.ONE_TO_ONE) {
478
599
  this._joins[aliasedName] = this.helper.joinOneToReference(prop, fromAlias, alias, type, cond);
@@ -493,24 +614,50 @@ class QueryBuilder {
493
614
  }
494
615
  const join = Object.keys(this._joins).find(k => field === k.substring(0, k.indexOf('#')));
495
616
  if (join && type === 'where') {
496
- return ret.push(...this.helper.mapJoinColumns(this.type, this._joins[join]));
617
+ return ret.push(...this.helper.mapJoinColumns(this.type ?? enums_1.QueryType.SELECT, this._joins[join]));
497
618
  }
498
619
  const [a, f] = this.helper.splitField(field);
499
620
  const prop = this.helper.getProperty(f, a);
621
+ /* istanbul ignore next */
500
622
  if (prop && [core_1.ReferenceType.ONE_TO_MANY, core_1.ReferenceType.MANY_TO_MANY].includes(prop.reference)) {
501
623
  return;
502
624
  }
625
+ if (prop?.embedded) {
626
+ const fieldName = this.helper.mapper(prop.fieldNames[0], this.type);
627
+ ret.push(fieldName);
628
+ return;
629
+ }
630
+ if (prop?.reference === core_1.ReferenceType.EMBEDDED) {
631
+ if (prop.object) {
632
+ ret.push(this.helper.mapper(prop.fieldNames[0], this.type));
633
+ }
634
+ else {
635
+ const nest = (prop) => {
636
+ for (const childProp of Object.values(prop.embeddedProps)) {
637
+ if (childProp.fieldNames) {
638
+ ret.push(this.helper.mapper(childProp.fieldNames[0], this.type));
639
+ }
640
+ else {
641
+ nest(childProp);
642
+ }
643
+ }
644
+ };
645
+ nest(prop);
646
+ }
647
+ return;
648
+ }
503
649
  ret.push(this.helper.mapper(field, this.type));
504
650
  });
505
- const meta = this.metadata.find(this.entityName);
651
+ const meta = this.mainAlias.metadata;
506
652
  /* istanbul ignore next */
507
653
  const requiresSQLConversion = meta?.props.filter(p => p.customType?.convertToJSValueSQL) ?? [];
508
- if (this.flags.has(core_1.QueryFlag.CONVERT_CUSTOM_TYPES) && (fields.includes('*') || fields.includes(`${this.alias}.*`)) && requiresSQLConversion.length > 0) {
654
+ if (this.flags.has(core_1.QueryFlag.CONVERT_CUSTOM_TYPES) && (fields.includes('*') || fields.includes(`${this.mainAlias.aliasName}.*`)) && requiresSQLConversion.length > 0) {
509
655
  requiresSQLConversion.forEach(p => ret.push(this.helper.mapper(p.name, this.type)));
510
656
  }
511
657
  Object.keys(this._populateMap).forEach(f => {
512
658
  if (!fields.includes(f.replace(/#\w+$/, '')) && type === 'where') {
513
- ret.push(...this.helper.mapJoinColumns(this.type, this._joins[f]));
659
+ const cols = this.helper.mapJoinColumns(this.type ?? enums_1.QueryType.SELECT, this._joins[f]);
660
+ ret.push(...cols);
514
661
  }
515
662
  if (this._joins[f].prop.reference !== core_1.ReferenceType.ONE_TO_ONE && this._joins[f].inverseJoinColumns) {
516
663
  this._joins[f].inverseJoinColumns.forEach(inverseJoinColumn => {
@@ -521,8 +668,8 @@ class QueryBuilder {
521
668
  return ret;
522
669
  }
523
670
  init(type, data, cond) {
671
+ this.ensureNotFinalized();
524
672
  this.type = type;
525
- this._aliasMap[this.alias] = this.entityName;
526
673
  if ([enums_1.QueryType.UPDATE, enums_1.QueryType.DELETE].includes(type) && core_1.Utils.hasObjectKeys(this._cond)) {
527
674
  throw new Error(`You are trying to call \`qb.where().${type.toLowerCase()}()\`. Calling \`qb.${type.toLowerCase()}()\` before \`qb.where()\` is required.`);
528
675
  }
@@ -530,6 +677,10 @@ class QueryBuilder {
530
677
  delete this._fields;
531
678
  }
532
679
  if (data) {
680
+ if (core_1.Utils.isEntity(data)) {
681
+ // data = helper(data).toJSON();
682
+ data = this.em?.getComparator().prepareEntity(data) ?? (0, core_1.serialize)(data);
683
+ }
533
684
  this._data = this.helper.processData(data, this.flags.has(core_1.QueryFlag.CONVERT_CUSTOM_TYPES));
534
685
  }
535
686
  if (cond) {
@@ -539,22 +690,23 @@ class QueryBuilder {
539
690
  }
540
691
  getQueryBase() {
541
692
  const qb = this.getKnex();
542
- const meta = this.metadata.find(this.entityName);
543
- const metaSchema = meta?.schema && meta.schema !== '*' ? meta.schema : undefined;
544
- const schema = this._schema ?? metaSchema ?? this.em?.config.get('schema');
693
+ const schema = this.getSchema(this.mainAlias);
545
694
  if (schema) {
546
695
  qb.withSchema(schema);
547
696
  }
548
697
  if (this._indexHint) {
549
- const alias = this.helper.isTableNameAliasRequired(this.type) ? ` as ${this.platform.quoteIdentifier(this.alias)}` : '';
698
+ const alias = this.helper.isTableNameAliasRequired(this.type) ? ` as ${this.platform.quoteIdentifier(this.mainAlias.aliasName)}` : '';
550
699
  const schemaQuoted = schema ? this.platform.quoteIdentifier(schema) + '.' : '';
551
- const tableName = schemaQuoted + this.platform.quoteIdentifier(this.helper.getTableName(this.entityName)) + alias;
700
+ const tableName = schemaQuoted + this.platform.quoteIdentifier(this.helper.getTableName(this.mainAlias.entityName)) + alias;
552
701
  qb.from(this.knex.raw(`${tableName} ${this._indexHint}`));
553
702
  }
554
703
  switch (this.type) {
555
704
  case enums_1.QueryType.SELECT:
556
705
  qb.select(this.prepareFields(this._fields));
557
- if (this.flags.has(core_1.QueryFlag.DISTINCT)) {
706
+ if (this._distinctOn) {
707
+ qb.distinctOn(this._distinctOn);
708
+ }
709
+ else if (this.flags.has(core_1.QueryFlag.DISTINCT)) {
558
710
  qb.distinct();
559
711
  }
560
712
  this.helper.processJoins(qb, this._joins, schema);
@@ -588,7 +740,7 @@ class QueryBuilder {
588
740
  if (!this.type) {
589
741
  this.select('*');
590
742
  }
591
- const meta = this.metadata.find(this.entityName);
743
+ const meta = this.mainAlias.metadata;
592
744
  if (meta && this.flags.has(core_1.QueryFlag.AUTO_JOIN_ONE_TO_ONE_OWNER)) {
593
745
  const relationsToPopulate = this._populate.map(({ field }) => field);
594
746
  meta.relations
@@ -600,7 +752,7 @@ class QueryBuilder {
600
752
  const [fromAlias, fromField] = this.helper.splitField(field);
601
753
  const aliasedField = `${fromAlias}.${fromField}`;
602
754
  const join = Object.keys(this._joins).find(k => `${aliasedField}#${this._joins[k].alias}` === k);
603
- if (this._joins[join] && this.helper.isOneToOneInverse(fromField)) {
755
+ if (join && this._joins[join] && this.helper.isOneToOneInverse(fromField)) {
604
756
  return this._populateMap[join] = this._joins[join].alias;
605
757
  }
606
758
  if (this.metadata.find(field)?.pivotTable) { // pivot table entity
@@ -608,16 +760,17 @@ class QueryBuilder {
608
760
  }
609
761
  else if (meta && this.helper.isOneToOneInverse(fromField)) {
610
762
  const prop = meta.properties[fromField];
611
- const alias = this.getNextAlias(prop.pivotTable ?? prop.type);
612
- this._joins[join] = this.helper.joinOneToReference(prop, this.alias, alias, 'leftJoin');
613
- this._populateMap[join] = this._joins[join].alias;
763
+ const alias = this.getNextAlias(prop.pivotEntity ?? prop.type);
764
+ const aliasedName = `${fromAlias}.${prop.name}#${alias}`;
765
+ this._joins[aliasedName] = this.helper.joinOneToReference(prop, this.mainAlias.aliasName, alias, 'leftJoin');
766
+ this._populateMap[aliasedName] = this._joins[aliasedName].alias;
614
767
  }
615
768
  });
616
- if (meta && (this._fields?.includes('*') || this._fields?.includes(`${this.alias}.*`))) {
769
+ if (meta && (this._fields?.includes('*') || this._fields?.includes(`${this.mainAlias.aliasName}.*`))) {
617
770
  meta.props
618
771
  .filter(prop => prop.formula && (!prop.lazy || this.flags.has(core_1.QueryFlag.INCLUDE_LAZY_FORMULAS)))
619
772
  .map(prop => {
620
- const alias = this.knex.ref(this.alias).toString();
773
+ const alias = this.knex.ref(this.mainAlias.aliasName).toString();
621
774
  const aliased = this.knex.ref(prop.fieldNames[0]).toString();
622
775
  return `${prop.formula(alias)} as ${aliased}`;
623
776
  })
@@ -627,13 +780,22 @@ class QueryBuilder {
627
780
  core_1.QueryHelper.processObjectParams(this._data);
628
781
  core_1.QueryHelper.processObjectParams(this._cond);
629
782
  core_1.QueryHelper.processObjectParams(this._having);
630
- this.finalized = true;
631
- if (meta && this.flags.has(core_1.QueryFlag.PAGINATE) && this._limit > 0) {
783
+ // automatically enable paginate flag when we detect to-many joins
784
+ if (!this.flags.has(core_1.QueryFlag.DISABLE_PAGINATE) && this.hasToManyJoins()) {
785
+ this.flags.add(core_1.QueryFlag.PAGINATE);
786
+ }
787
+ if (meta && this.flags.has(core_1.QueryFlag.PAGINATE) && (this._limit > 0 || this._offset > 0)) {
632
788
  this.wrapPaginateSubQuery(meta);
633
789
  }
634
790
  if (meta && (this.flags.has(core_1.QueryFlag.UPDATE_SUB_QUERY) || this.flags.has(core_1.QueryFlag.DELETE_SUB_QUERY))) {
635
791
  this.wrapModifySubQuery(meta);
636
792
  }
793
+ this.finalized = true;
794
+ }
795
+ hasToManyJoins() {
796
+ return Object.values(this._joins).some(join => {
797
+ return [core_1.ReferenceType.ONE_TO_MANY, core_1.ReferenceType.MANY_TO_MANY].includes(join.prop.reference);
798
+ });
637
799
  }
638
800
  wrapPaginateSubQuery(meta) {
639
801
  const pks = this.prepareFields(meta.primaryKeys, 'sub-query');
@@ -645,28 +807,38 @@ class QueryBuilder {
645
807
  const orderBy = [];
646
808
  for (const orderMap of this._orderBy) {
647
809
  for (const [field, direction] of Object.entries(orderMap)) {
810
+ const [a, f] = this.helper.splitField(field);
811
+ const prop = this.helper.getProperty(f, a);
812
+ const type = this.platform.castColumn(prop);
648
813
  orderBy.push({
649
- [`min(${this.ref(field)})`]: direction,
814
+ [`min(${this.ref(this.helper.mapper(field, this.type, undefined, null))}${type})`]: direction,
650
815
  });
651
816
  }
652
817
  }
653
818
  subQuery.orderBy(orderBy);
654
819
  }
655
820
  subQuery.finalized = true;
656
- const knexQuery = subQuery.as(this.alias).clearSelect().select(pks);
821
+ const knexQuery = subQuery.as(this.mainAlias.aliasName).clearSelect().select(pks);
657
822
  // multiple sub-queries are needed to get around mysql limitations with order by + limit + where in + group by (o.O)
658
823
  // https://stackoverflow.com/questions/17892762/mysql-this-version-of-mysql-doesnt-yet-support-limit-in-all-any-some-subqu
659
824
  const subSubQuery = this.getKnex().select(pks).from(knexQuery);
660
825
  this._limit = undefined;
661
826
  this._offset = undefined;
827
+ const cond = this._cond;
662
828
  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
+ }
663
835
  }
664
836
  wrapModifySubQuery(meta) {
665
837
  const subQuery = this.clone();
666
838
  subQuery.finalized = true;
667
839
  // wrap one more time to get around MySQL limitations
668
840
  // https://stackoverflow.com/questions/45494/mysql-error-1093-cant-specify-target-table-for-update-in-from-clause
669
- const subSubQuery = this.getKnex().select(this.prepareFields(meta.primaryKeys)).from(subQuery.as(this.alias));
841
+ const subSubQuery = this.getKnex().select(this.prepareFields(meta.primaryKeys)).from(subQuery.as(this.mainAlias.aliasName));
670
842
  const method = this.flags.has(core_1.QueryFlag.UPDATE_SUB_QUERY) ? 'update' : 'delete';
671
843
  this._cond = {}; // otherwise we would trigger validation error
672
844
  this[method](this._data).where({
@@ -675,14 +847,76 @@ class QueryBuilder {
675
847
  }
676
848
  autoJoinPivotTable(field) {
677
849
  const pivotMeta = this.metadata.find(field);
678
- const owner = pivotMeta.props.find(prop => prop.reference === core_1.ReferenceType.MANY_TO_ONE && prop.owner);
679
- const inverse = pivotMeta.props.find(prop => prop.reference === core_1.ReferenceType.MANY_TO_ONE && !prop.owner);
850
+ const owner = pivotMeta.relations[0];
851
+ const inverse = pivotMeta.relations[1];
680
852
  const prop = this._cond[pivotMeta.name + '.' + owner.name] || this._orderBy[pivotMeta.name + '.' + owner.name] ? inverse : owner;
681
853
  const pivotAlias = this.getNextAlias(pivotMeta.name);
682
- this._joins[field] = this.helper.joinPivotTable(field, prop, this.alias, pivotAlias, 'leftJoin');
854
+ this._joins[field] = this.helper.joinPivotTable(field, prop, this.mainAlias.aliasName, pivotAlias, 'leftJoin');
683
855
  core_1.Utils.renameKey(this._cond, `${field}.${owner.name}`, core_1.Utils.getPrimaryKeyHash(owner.fieldNames.map(fieldName => `${pivotAlias}.${fieldName}`)));
684
856
  core_1.Utils.renameKey(this._cond, `${field}.${inverse.name}`, core_1.Utils.getPrimaryKeyHash(inverse.fieldNames.map(fieldName => `${pivotAlias}.${fieldName}`)));
685
857
  this._populateMap[field] = this._joins[field].alias;
686
858
  }
859
+ getSchema(alias) {
860
+ const { metadata } = alias;
861
+ const metaSchema = metadata?.schema && metadata.schema !== '*' ? metadata.schema : undefined;
862
+ return this._schema ?? metaSchema ?? this.em?.config.get('schema');
863
+ }
864
+ createAlias(entityName, aliasName, subQuery) {
865
+ const metadata = this.metadata.find(entityName);
866
+ const alias = { aliasName, entityName, metadata, subQuery };
867
+ this._aliases[aliasName] = alias;
868
+ return alias;
869
+ }
870
+ createMainAlias(entityName, aliasName, subQuery) {
871
+ this._mainAlias = this.createAlias(entityName, aliasName, subQuery);
872
+ this._helper = this.createQueryBuilderHelper();
873
+ return this._mainAlias;
874
+ }
875
+ fromSubQuery(target, aliasName) {
876
+ const subQuery = target.getKnexQuery();
877
+ const { entityName } = target.mainAlias;
878
+ aliasName ?? (aliasName = this.getNextAlias(entityName));
879
+ this.createMainAlias(entityName, aliasName, subQuery);
880
+ }
881
+ fromEntityName(entityName, aliasName) {
882
+ aliasName ?? (aliasName = this._mainAlias?.aliasName ?? this.getNextAlias(entityName));
883
+ this.createMainAlias(entityName, aliasName);
884
+ }
885
+ createQueryBuilderHelper() {
886
+ return new QueryBuilderHelper_1.QueryBuilderHelper(this.mainAlias.entityName, this.mainAlias.aliasName, this._aliases, this.subQueries, this.knex, this.driver);
887
+ }
888
+ ensureFromClause() {
889
+ /* istanbul ignore next */
890
+ if (!this._mainAlias) {
891
+ throw new Error(`Cannot proceed to build a query because the main alias is not set.`);
892
+ }
893
+ }
894
+ ensureNotFinalized() {
895
+ if (this.finalized) {
896
+ throw new Error('This QueryBuilder instance is already finalized, clone it first if you want to modify it.');
897
+ }
898
+ }
899
+ /* istanbul ignore next */
900
+ [util_1.inspect.custom](depth) {
901
+ const object = { ...this };
902
+ const hidden = ['metadata', 'driver', 'context', 'platform', 'knex', 'type'];
903
+ Object.keys(object).filter(k => k.startsWith('_')).forEach(k => delete object[k]);
904
+ Object.keys(object).filter(k => object[k] == null).forEach(k => delete object[k]);
905
+ hidden.forEach(k => delete object[k]);
906
+ let prefix = this.type ? this.type.substring(0, 1) + this.type.toLowerCase().substring(1) : '';
907
+ if (this._data) {
908
+ object.data = this._data;
909
+ }
910
+ if (!core_1.Utils.isEmpty(this._cond)) {
911
+ object.where = this._cond;
912
+ }
913
+ if (this._onConflict?.[0]) {
914
+ prefix = 'Upsert';
915
+ object.onConflict = this._onConflict[0];
916
+ }
917
+ const name = this._mainAlias ? `${prefix}QueryBuilder<${this._mainAlias?.entityName}>` : 'QueryBuilder';
918
+ const ret = (0, util_1.inspect)(object, { depth });
919
+ return ret === '[Object]' ? `[${name}]` : name + ' ' + ret;
920
+ }
687
921
  }
688
922
  exports.QueryBuilder = QueryBuilder;