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

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
@@ -5,9 +5,12 @@ const core_1 = require("@mikro-orm/core");
5
5
  const query_1 = require("./query");
6
6
  const SqlEntityManager_1 = require("./SqlEntityManager");
7
7
  class AbstractSqlDriver extends core_1.DatabaseDriver {
8
+ [core_1.EntityManagerType];
9
+ connection;
10
+ replicas = [];
11
+ platform;
8
12
  constructor(config, platform, connection, connector) {
9
13
  super(config, connector);
10
- this.replicas = [];
11
14
  this.connection = new connection(this.config);
12
15
  this.replicas = this.createReplicas(conf => new connection(this.config, conf, 'read'));
13
16
  this.platform = platform;
@@ -26,19 +29,31 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
26
29
  }
27
30
  const populate = this.autoJoinOneToOneOwner(meta, options.populate, options.fields);
28
31
  const joinedProps = this.joinedProps(meta, populate);
29
- const qb = this.createQueryBuilder(entityName, options.ctx, options.connectionType, false);
32
+ const qb = this.createQueryBuilder(entityName, options.ctx, options.connectionType, false, options.logging);
30
33
  const fields = this.buildFields(meta, populate, joinedProps, qb, options.fields);
31
34
  const joinedPropsOrderBy = this.buildJoinedPropsOrderBy(entityName, qb, meta, joinedProps);
35
+ const orderBy = [...core_1.Utils.asArray(options.orderBy), ...joinedPropsOrderBy];
32
36
  if (core_1.Utils.isPrimaryKey(where, meta.compositePK)) {
33
37
  where = { [core_1.Utils.getPrimaryKeyHash(meta.primaryKeys)]: where };
34
38
  }
39
+ const { first, last, before, after } = options;
40
+ const isCursorPagination = [first, last, before, after].some(v => v != null);
35
41
  qb.select(fields)
36
42
  .populate(populate, joinedProps.length > 0 ? options.populateWhere : undefined)
37
43
  .where(where)
38
- .orderBy([...core_1.Utils.asArray(options.orderBy), ...joinedPropsOrderBy])
39
44
  .groupBy(options.groupBy)
40
45
  .having(options.having)
46
+ .indexHint(options.indexHint)
47
+ .comment(options.comments)
48
+ .hintComment(options.hintComments)
41
49
  .withSchema(this.getSchemaName(meta, options));
50
+ if (isCursorPagination) {
51
+ const { orderBy: newOrderBy, where } = this.processCursorOptions(meta, options, orderBy);
52
+ qb.andWhere(where).orderBy(newOrderBy);
53
+ }
54
+ else {
55
+ qb.orderBy(orderBy);
56
+ }
42
57
  if (options.limit !== undefined) {
43
58
  qb.limit(options.limit, options.offset);
44
59
  }
@@ -47,8 +62,11 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
47
62
  }
48
63
  core_1.Utils.asArray(options.flags).forEach(flag => qb.setFlag(flag));
49
64
  const result = await this.rethrow(qb.execute('all'));
65
+ if (isCursorPagination && !first && !!last) {
66
+ result.reverse();
67
+ }
50
68
  if (joinedProps.length > 0) {
51
- return this.mergeJoinedResult(result, meta);
69
+ return this.mergeJoinedResult(result, meta, joinedProps);
52
70
  }
53
71
  return result;
54
72
  }
@@ -61,7 +79,7 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
61
79
  opts.limit = 1;
62
80
  }
63
81
  if (opts.limit > 0 && !opts.flags?.includes(core_1.QueryFlag.DISABLE_PAGINATE)) {
64
- opts.flags ?? (opts.flags = []);
82
+ opts.flags ??= [];
65
83
  opts.flags.push(core_1.QueryFlag.DISABLE_PAGINATE);
66
84
  }
67
85
  const res = await this.find(entityName, where, opts);
@@ -79,7 +97,7 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
79
97
  const em = this.createEntityManager(false);
80
98
  em.setTransactionContext(options.ctx);
81
99
  const res = meta.expression(em, where, options);
82
- if (res instanceof (query_1.QueryBuilder)) {
100
+ if (res instanceof query_1.QueryBuilder) {
83
101
  return this.wrapVirtualExpressionInSubquery(meta, res.getFormattedQuery(), where, options);
84
102
  }
85
103
  /* istanbul ignore next */
@@ -97,7 +115,7 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
97
115
  const em = this.createEntityManager(false);
98
116
  em.setTransactionContext(options.ctx);
99
117
  const res = meta.expression(em, where, options);
100
- if (res instanceof (query_1.QueryBuilder)) {
118
+ if (res instanceof query_1.QueryBuilder) {
101
119
  return this.wrapVirtualExpressionInSubquery(meta, res.getFormattedQuery(), where, options, query_1.QueryType.COUNT);
102
120
  }
103
121
  /* istanbul ignore next */
@@ -105,14 +123,17 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
105
123
  }
106
124
  async wrapVirtualExpressionInSubquery(meta, expression, where, options, type = query_1.QueryType.SELECT) {
107
125
  const qb = this.createQueryBuilder(meta.className, options?.ctx, options.connectionType, options.convertCustomTypes)
108
- .limit(options?.limit, options?.offset);
126
+ .limit(options?.limit, options?.offset)
127
+ .indexHint(options.indexHint)
128
+ .comment(options.comments)
129
+ .hintComment(options.hintComments);
109
130
  if (options.orderBy) {
110
131
  qb.orderBy(options.orderBy);
111
132
  }
112
133
  qb.where(where);
113
134
  const kqb = qb.getKnexQuery().clear('select');
114
135
  if (type === query_1.QueryType.COUNT) {
115
- kqb.select(qb.raw('count(*) as count'));
136
+ kqb.select(this.connection.getKnex().raw('count(*) as count'));
116
137
  }
117
138
  else { // select
118
139
  kqb.select('*');
@@ -154,11 +175,12 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
154
175
  }));
155
176
  if (!hasPK) {
156
177
  // initialize empty collections
157
- if ([core_1.ReferenceType.MANY_TO_MANY, core_1.ReferenceType.ONE_TO_MANY].includes(relation.reference)) {
158
- result[relation.name] = result[relation.name] || [];
178
+ if ([core_1.ReferenceKind.MANY_TO_MANY, core_1.ReferenceKind.ONE_TO_MANY].includes(relation.kind)) {
179
+ result[relation.name] ??= [];
159
180
  }
160
181
  return;
161
182
  }
183
+ const tz = this.platform.getTimezone();
162
184
  meta2.props
163
185
  .filter(prop => this.platform.shouldHaveColumn(prop, p.children || []))
164
186
  .forEach(prop => {
@@ -166,6 +188,11 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
166
188
  relationPojo[prop.name] = prop.fieldNames.map(name => root[`${relationAlias}__${name}`]);
167
189
  prop.fieldNames.map(name => delete root[`${relationAlias}__${name}`]);
168
190
  }
191
+ else if (prop.runtimeType === 'Date') {
192
+ const alias = `${relationAlias}__${prop.fieldNames[0]}`;
193
+ relationPojo[prop.name] = (typeof root[alias] === 'string' ? new Date(root[alias]) : root[alias]);
194
+ delete root[alias];
195
+ }
169
196
  else {
170
197
  const alias = `${relationAlias}__${prop.fieldNames[0]}`;
171
198
  relationPojo[prop.name] = root[alias];
@@ -179,8 +206,8 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
179
206
  else {
180
207
  map[key] = result;
181
208
  }
182
- if ([core_1.ReferenceType.MANY_TO_MANY, core_1.ReferenceType.ONE_TO_MANY].includes(relation.reference)) {
183
- result[relation.name] = result[relation.name] || [];
209
+ if ([core_1.ReferenceKind.MANY_TO_MANY, core_1.ReferenceKind.ONE_TO_MANY].includes(relation.kind)) {
210
+ result[relation.name] ??= [];
184
211
  this.appendToCollection(meta2, result[relation.name], relationPojo);
185
212
  }
186
213
  else {
@@ -207,6 +234,9 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
207
234
  return this.countVirtual(entityName, where, options);
208
235
  }
209
236
  const qb = this.createQueryBuilder(entityName, options.ctx, options.connectionType, false)
237
+ .indexHint(options.indexHint)
238
+ .comment(options.comments)
239
+ .hintComment(options.hintComments)
210
240
  .groupBy(options.groupBy)
211
241
  .having(options.having)
212
242
  .populate(options.populate ?? [])
@@ -215,7 +245,7 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
215
245
  return this.rethrow(qb.getCount());
216
246
  }
217
247
  async nativeInsert(entityName, data, options = {}) {
218
- options.convertCustomTypes ?? (options.convertCustomTypes = true);
248
+ options.convertCustomTypes ??= true;
219
249
  const meta = this.metadata.find(entityName);
220
250
  const collections = this.extractManyToMany(entityName, data);
221
251
  const pks = meta?.primaryKeys ?? [this.config.getNamingStrategy().referenceColumnName()];
@@ -235,16 +265,20 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
235
265
  return res;
236
266
  }
237
267
  async nativeInsertMany(entityName, data, options = {}) {
238
- options.processCollections ?? (options.processCollections = true);
239
- options.convertCustomTypes ?? (options.convertCustomTypes = true);
268
+ options.processCollections ??= true;
269
+ options.convertCustomTypes ??= true;
240
270
  const meta = this.metadata.find(entityName);
241
271
  const collections = options.processCollections ? data.map(d => this.extractManyToMany(entityName, d)) : [];
242
272
  const pks = this.getPrimaryKeyFields(entityName);
243
273
  const set = new Set();
244
- data.forEach(row => Object.keys(row).forEach(k => set.add(k)));
274
+ data.forEach(row => core_1.Utils.keys(row).forEach(k => set.add(k)));
245
275
  const props = [...set].map(name => meta?.properties[name] ?? { name, fieldNames: [name] });
246
- const fields = core_1.Utils.flatten(props.map(prop => prop.fieldNames));
276
+ let fields = core_1.Utils.flatten(props.map(prop => prop.fieldNames));
277
+ const duplicates = core_1.Utils.findDuplicates(fields);
247
278
  const params = [];
279
+ if (duplicates.length) {
280
+ fields = core_1.Utils.unique(fields);
281
+ }
248
282
  /* istanbul ignore next */
249
283
  const tableName = meta ? this.getTableName(meta, options) : this.platform.quoteIdentifier(entityName);
250
284
  let sql = `insert into ${tableName} `;
@@ -255,29 +289,45 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
255
289
  else {
256
290
  sql += ' ' + data.map(() => `select null as ${this.platform.quoteIdentifier(pks[0])}`).join(' union all ');
257
291
  }
292
+ const addParams = (prop, row) => {
293
+ if (options.convertCustomTypes && prop.customType) {
294
+ params.push(prop.customType.convertToDatabaseValue(row[prop.name], this.platform, { key: prop.name, mode: 'query-data' }));
295
+ return;
296
+ }
297
+ params.push(row[prop.name]);
298
+ };
258
299
  if (fields.length > 0 || this.platform.usesDefaultKeyword()) {
259
300
  sql += data.map(row => {
260
301
  const keys = [];
261
302
  props.forEach(prop => {
262
303
  if (prop.fieldNames.length > 1) {
263
- params.push(...(row[prop.name] ?? prop.fieldNames.map(() => null)));
264
- keys.push(...prop.fieldNames.map(_ => '?'));
304
+ const param = [...row[prop.name] ?? prop.fieldNames.map(() => null)];
305
+ const key = (row[prop.name] ?? prop.fieldNames).map(() => '?');
306
+ prop.fieldNames.forEach((field, idx) => {
307
+ if (duplicates.includes(field)) {
308
+ param.splice(idx, 1);
309
+ key.splice(idx, 1);
310
+ }
311
+ });
312
+ params.push(...param);
313
+ keys.push(...key);
265
314
  }
266
315
  else if (prop.customType && 'convertToDatabaseValueSQL' in prop.customType && !this.platform.isRaw(row[prop.name])) {
267
316
  keys.push(prop.customType.convertToDatabaseValueSQL('?', this.platform));
268
- params.push(row[prop.name]);
317
+ addParams(prop, row);
269
318
  }
270
319
  else {
271
- params.push(row[prop.name]);
320
+ addParams(prop, row);
272
321
  keys.push('?');
273
322
  }
274
323
  });
275
324
  return '(' + (keys.join(', ') || 'default') + ')';
276
325
  }).join(', ');
277
326
  }
278
- if (this.platform.usesReturningStatement()) {
279
- /* istanbul ignore next */
280
- const returningProps = meta.hydrateProps.filter(prop => prop.persist !== false && (prop.primary || prop.defaultRaw));
327
+ if (meta && this.platform.usesReturningStatement()) {
328
+ const returningProps = meta.props
329
+ .filter(prop => prop.persist !== false && ((prop.primary && prop.autoincrement) || prop.defaultRaw))
330
+ .filter(prop => !(prop.name in data[0]) || core_1.Utils.isRawSql(data[0][prop.name]));
281
331
  const returningFields = core_1.Utils.flatten(returningProps.map(prop => prop.fieldNames));
282
332
  /* istanbul ignore next */
283
333
  sql += returningFields.length > 0 ? ` returning ${returningFields.map(field => this.platform.quoteIdentifier(field)).join(', ')}` : '';
@@ -289,8 +339,8 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
289
339
  pk = data.map(d => core_1.Utils.getPrimaryKeyCond(d, pks));
290
340
  }
291
341
  else {
292
- res.row ?? (res.row = {});
293
- res.rows ?? (res.rows = []);
342
+ res.row ??= {};
343
+ res.rows ??= [];
294
344
  pk = data.map((d, i) => d[pks[0]] ?? res.rows[i]?.[pks[0]]).map(d => [d]);
295
345
  res.insertId = res.insertId || res.row[pks[0]];
296
346
  }
@@ -300,7 +350,7 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
300
350
  return res;
301
351
  }
302
352
  async nativeUpdate(entityName, where, data, options = {}) {
303
- options.convertCustomTypes ?? (options.convertCustomTypes = true);
353
+ options.convertCustomTypes ??= true;
304
354
  const meta = this.metadata.find(entityName);
305
355
  const pks = this.getPrimaryKeyFields(entityName);
306
356
  const collections = this.extractManyToMany(entityName, data);
@@ -314,11 +364,18 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
314
364
  .withSchema(this.getSchemaName(meta, options));
315
365
  if (options.upsert) {
316
366
  /* istanbul ignore next */
317
- const uniqueFields = core_1.Utils.isPlainObject(where) ? Object.keys(where) : meta.primaryKeys;
318
- /* istanbul ignore next */
367
+ const uniqueFields = options.onConflictFields ?? (core_1.Utils.isPlainObject(where) ? core_1.Utils.keys(where) : meta.primaryKeys);
368
+ const returning = (0, core_1.getOnConflictReturningFields)(meta, data, uniqueFields, options);
319
369
  qb.insert(data)
320
- .onConflict(uniqueFields.map(p => meta?.properties[p]?.fieldNames[0] ?? p))
321
- .merge(Object.keys(data).filter(f => !uniqueFields.includes(f)));
370
+ .onConflict(uniqueFields)
371
+ .returning(returning);
372
+ if (!options.onConflictAction || options.onConflictAction === 'merge') {
373
+ const fields = (0, core_1.getOnConflictFields)(data, uniqueFields, options);
374
+ qb.merge(fields);
375
+ }
376
+ if (options.onConflictAction === 'ignore') {
377
+ qb.ignore();
378
+ }
322
379
  }
323
380
  else {
324
381
  qb.update(data).where(where);
@@ -331,12 +388,36 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
331
388
  return res;
332
389
  }
333
390
  async nativeUpdateMany(entityName, where, data, options = {}) {
334
- options.processCollections ?? (options.processCollections = true);
335
- options.convertCustomTypes ?? (options.convertCustomTypes = true);
391
+ options.processCollections ??= true;
392
+ options.convertCustomTypes ??= true;
336
393
  const meta = this.metadata.get(entityName);
394
+ if (options.upsert) {
395
+ const uniqueFields = options.onConflictFields ?? (core_1.Utils.isPlainObject(where[0]) ? Object.keys(where[0]) : meta.primaryKeys);
396
+ const qb = this.createQueryBuilder(entityName, options.ctx, 'write', options.convertCustomTypes).withSchema(this.getSchemaName(meta, options));
397
+ const returning = (0, core_1.getOnConflictReturningFields)(meta, data[0], uniqueFields, options);
398
+ qb.insert(data)
399
+ .onConflict(uniqueFields)
400
+ .returning(returning);
401
+ if (!options.onConflictAction || options.onConflictAction === 'merge') {
402
+ const fields = (0, core_1.getOnConflictFields)(data[0], uniqueFields, options);
403
+ qb.merge(fields);
404
+ }
405
+ if (options.onConflictAction === 'ignore') {
406
+ qb.ignore();
407
+ }
408
+ return qb.execute('run', false);
409
+ }
337
410
  const collections = options.processCollections ? data.map(d => this.extractManyToMany(entityName, d)) : [];
338
411
  const keys = new Set();
339
- data.forEach(row => Object.keys(row).forEach(k => keys.add(k)));
412
+ const returning = new Set();
413
+ data.forEach(row => {
414
+ core_1.Utils.keys(row).forEach(k => {
415
+ keys.add(k);
416
+ if (core_1.Utils.isRawSql(row[k])) {
417
+ returning.add(k);
418
+ }
419
+ });
420
+ });
340
421
  const pkCond = core_1.Utils.flatten(meta.primaryKeys.map(pk => meta.properties[pk].fieldNames)).map(pk => `${this.platform.quoteIdentifier(pk)} = ?`).join(' and ');
341
422
  const params = [];
342
423
  let sql = `update ${this.getTableName(meta, options)} set `;
@@ -365,7 +446,7 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
365
446
  const versionProperty = meta.properties[meta.versionProperty];
366
447
  const quotedFieldName = this.platform.quoteIdentifier(versionProperty.fieldNames[0]);
367
448
  sql += `${quotedFieldName} = `;
368
- if (versionProperty.type.toLowerCase() === 'date') {
449
+ if (versionProperty.runtimeType === 'Date') {
369
450
  sql += this.platform.getCurrentTimestampSQL(versionProperty.length);
370
451
  }
371
452
  else {
@@ -396,6 +477,11 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
396
477
  return '?';
397
478
  });
398
479
  sql += ` in (${conds.join(', ')})`;
480
+ if (this.platform.usesReturningStatement() && returning.size > 0) {
481
+ const returningFields = core_1.Utils.flatten([...returning].map(prop => meta.properties[prop].fieldNames));
482
+ /* istanbul ignore next */
483
+ sql += returningFields.length > 0 ? ` returning ${returningFields.map(field => this.platform.quoteIdentifier(field)).join(', ')}` : '';
484
+ }
399
485
  const res = await this.rethrow(this.execute(sql, params, 'run', options.ctx));
400
486
  for (let i = 0; i < collections.length; i++) {
401
487
  await this.processManyToMany(meta, where[i], collections[i], false, options);
@@ -430,30 +516,53 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
430
516
  deleteDiff.push(...snapshot);
431
517
  insertDiff.push(...current);
432
518
  }
433
- if (coll.property.reference === core_1.ReferenceType.ONE_TO_MANY) {
519
+ if (coll.property.kind === core_1.ReferenceKind.ONE_TO_MANY) {
434
520
  const cols = coll.property.referencedColumnNames;
435
521
  const qb = this.createQueryBuilder(coll.property.type, ctx, 'write')
436
- .withSchema(this.getSchemaName(meta, options))
437
- .update({ [coll.property.mappedBy]: pks })
522
+ .withSchema(this.getSchemaName(meta, options));
523
+ if (coll.getSnapshot() === undefined) {
524
+ if (coll.property.orphanRemoval) {
525
+ const kqb = qb.delete({ [coll.property.mappedBy]: pks })
526
+ .getKnexQuery()
527
+ .whereNotIn(cols, insertDiff);
528
+ return this.rethrow(this.execute(kqb));
529
+ }
530
+ const kqb = qb.update({ [coll.property.mappedBy]: null })
531
+ .getKnexQuery()
532
+ .whereNotIn(cols, insertDiff);
533
+ return this.rethrow(this.execute(kqb));
534
+ }
535
+ const kqb = qb.update({ [coll.property.mappedBy]: pks })
438
536
  .getKnexQuery()
439
537
  .whereIn(cols, insertDiff);
440
- return this.rethrow(this.execute(qb));
538
+ return this.rethrow(this.execute(kqb));
441
539
  }
442
540
  /* istanbul ignore next */
443
541
  const ownerSchema = wrapped.getSchema() === '*' ? this.config.get('schema') : wrapped.getSchema();
444
542
  const pivotMeta = this.metadata.find(coll.property.pivotEntity);
445
543
  if (pivotMeta.schema === '*') {
446
544
  /* istanbul ignore next */
447
- options ?? (options = {});
545
+ options ??= {};
448
546
  options.schema = ownerSchema;
449
547
  }
450
548
  return this.rethrow(this.updateCollectionDiff(meta, coll.property, pks, deleteDiff, insertDiff, options));
451
549
  }
452
550
  async loadFromPivotTable(prop, owners, where = {}, orderBy, ctx, options) {
453
551
  const pivotProp2 = this.getPivotInverseProperty(prop);
552
+ const prop2 = prop.targetMeta.properties[prop.mappedBy ?? prop.inversedBy];
454
553
  const ownerMeta = this.metadata.find(pivotProp2.type);
455
554
  const pivotMeta = this.metadata.find(prop.pivotEntity);
456
- const cond = { [`${prop.pivotEntity}.${pivotProp2.name}`]: { $in: ownerMeta.compositePK ? owners : owners.map(o => o[0]) } };
555
+ options = { ...options };
556
+ const qb = this.createQueryBuilder(prop.type, ctx, options.connectionType)
557
+ .withSchema(this.getSchemaName(prop.targetMeta, options))
558
+ .indexHint(options.indexHint)
559
+ .comment(options.comments)
560
+ .hintComment(options.hintComments);
561
+ const pivotAlias = qb.getNextAlias(pivotMeta.tableName);
562
+ const pivotKey = pivotProp2.joinColumns.map(column => `${pivotAlias}.${column}`).join(core_1.Utils.PK_SEPARATOR);
563
+ const cond = {
564
+ [pivotKey]: { $in: ownerMeta.compositePK ? owners : owners.map(o => o[0]) },
565
+ };
457
566
  /* istanbul ignore if */
458
567
  if (!core_1.Utils.isEmpty(where) && Object.keys(where).every(k => core_1.Utils.isOperator(k, false))) {
459
568
  where = cond;
@@ -461,22 +570,32 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
461
570
  else {
462
571
  where = { ...where, ...cond };
463
572
  }
464
- orderBy = this.getPivotOrderBy(prop, orderBy);
465
- const qb = this.createQueryBuilder(prop.type, ctx, options?.connectionType)
466
- .unsetFlag(core_1.QueryFlag.CONVERT_CUSTOM_TYPES)
467
- .withSchema(this.getSchemaName(prop.targetMeta, options));
468
- const populate = this.autoJoinOneToOneOwner(prop.targetMeta, [{ field: prop.pivotEntity }]);
469
- const fields = this.buildFields(prop.targetMeta, (options?.populate ?? []), [], qb, options?.fields);
470
- qb.select(fields).populate(populate).where(where).orderBy(orderBy).setLockMode(options?.lockMode, options?.lockTableAliases);
471
- if (owners.length === 1 && (options?.offset != null || options?.limit != null)) {
573
+ orderBy = this.getPivotOrderBy(prop, pivotAlias, orderBy);
574
+ const populate = this.autoJoinOneToOneOwner(prop.targetMeta, []);
575
+ const fields = this.buildFields(prop.targetMeta, (options.populate ?? []), [], qb, options.fields);
576
+ const k1 = !prop.owner ? 'joinColumns' : 'inverseJoinColumns';
577
+ const k2 = prop.owner ? 'joinColumns' : 'inverseJoinColumns';
578
+ const cols = [
579
+ ...prop[k1].map(col => `${pivotAlias}.${col} as fk__${col}`),
580
+ ...prop[k2].map(col => `${pivotAlias}.${col} as fk__${col}`),
581
+ ];
582
+ fields.push(...cols);
583
+ qb.select(fields)
584
+ .join(prop2.name, pivotAlias, {}, 'pivotJoin', undefined, options.schema)
585
+ .populate(populate)
586
+ .where(where)
587
+ .orderBy(orderBy)
588
+ .setLockMode(options.lockMode, options.lockTableAliases);
589
+ if (owners.length === 1 && (options.offset != null || options.limit != null)) {
472
590
  qb.limit(options.limit, options.offset);
473
591
  }
474
- if (prop.targetMeta.schema !== '*' && pivotMeta.schema === '*' && options?.schema) {
592
+ const schema = this.config.get('schema');
593
+ if (prop.targetMeta.schema !== '*' && pivotMeta.schema === '*' && options.schema) {
475
594
  // eslint-disable-next-line dot-notation
476
595
  qb['finalize']();
477
596
  // eslint-disable-next-line dot-notation
478
597
  Object.values(qb['_joins']).forEach(join => {
479
- join.schema = options.schema;
598
+ join.schema = schema;
480
599
  });
481
600
  }
482
601
  const items = owners.length ? await this.rethrow(qb.execute('all')) : [];
@@ -500,6 +619,18 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
500
619
  });
501
620
  return map;
502
621
  }
622
+ getPivotOrderBy(prop, pivotAlias, orderBy) {
623
+ if (!core_1.Utils.isEmpty(orderBy)) {
624
+ return orderBy;
625
+ }
626
+ if (!core_1.Utils.isEmpty(prop.orderBy)) {
627
+ return core_1.Utils.asArray(prop.orderBy);
628
+ }
629
+ if (prop.fixedOrder) {
630
+ return [{ [`${pivotAlias}.${prop.fixedOrderColumn}`]: core_1.QueryOrder.ASC }];
631
+ }
632
+ return [];
633
+ }
503
634
  async execute(queryOrKnex, params = [], method = 'all', ctx) {
504
635
  return this.rethrow(this.connection.execute(queryOrKnex, params, method, ctx));
505
636
  }
@@ -512,7 +643,7 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
512
643
  }
513
644
  const relationsToPopulate = populate.map(({ field }) => field);
514
645
  const toPopulate = meta.relations
515
- .filter(prop => prop.reference === core_1.ReferenceType.ONE_TO_ONE && !prop.owner && !relationsToPopulate.includes(prop.name))
646
+ .filter(prop => prop.kind === core_1.ReferenceKind.ONE_TO_ONE && !prop.owner && !relationsToPopulate.includes(prop.name))
516
647
  .filter(prop => fields.length === 0 || fields.some(f => prop.name === f || prop.name.startsWith(`${String(f)}.`)))
517
648
  .map(prop => ({ field: prop.name, strategy: prop.strategy }));
518
649
  return [...populate, ...toPopulate];
@@ -520,23 +651,30 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
520
651
  joinedProps(meta, populate) {
521
652
  return populate.filter(p => {
522
653
  const prop = meta.properties[p.field] || {};
523
- return (p.strategy || prop.strategy || this.config.get('loadStrategy')) === core_1.LoadStrategy.JOINED && prop.reference !== core_1.ReferenceType.SCALAR;
654
+ return (p.strategy || prop.strategy || this.config.get('loadStrategy')) === core_1.LoadStrategy.JOINED && prop.kind !== core_1.ReferenceKind.SCALAR;
524
655
  });
525
656
  }
526
657
  /**
527
658
  * @internal
528
659
  */
529
- mergeJoinedResult(rawResults, meta) {
660
+ mergeJoinedResult(rawResults, meta, joinedProps) {
530
661
  // group by the root entity primary key first
531
662
  const map = {};
532
663
  const res = [];
533
664
  rawResults.forEach(item => {
534
665
  const pk = core_1.Utils.getCompositeKeyHash(item, meta);
535
666
  if (map[pk]) {
536
- map[pk].push(item);
667
+ joinedProps.forEach(hint => {
668
+ // Sometimes we might join a M:N relation with additional filter on the target entity, and as a result, we get
669
+ // the first result with `null` for all target values, which is mapped as empty array. When we see that happen,
670
+ // we need to merge the results of the next item.
671
+ if (Array.isArray(map[pk][hint.field]) && Array.isArray(item[hint.field]) && map[pk][hint.field].length === 0) {
672
+ item[hint.field].forEach((el) => map[pk][hint.field].push(el));
673
+ }
674
+ });
537
675
  }
538
676
  else {
539
- map[pk] = [item];
677
+ map[pk] = item;
540
678
  res.push(item);
541
679
  }
542
680
  });
@@ -545,14 +683,17 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
545
683
  getFieldsForJoinedLoad(qb, meta, explicitFields, populate = [], parentTableAlias, parentJoinPath) {
546
684
  const fields = [];
547
685
  const joinedProps = this.joinedProps(meta, populate);
686
+ if (explicitFields?.includes('*')) {
687
+ fields.push('*');
688
+ }
548
689
  const shouldHaveColumn = (prop, populate, fields) => {
549
690
  if (!this.platform.shouldHaveColumn(prop, populate)) {
550
691
  return false;
551
692
  }
552
693
  if (!fields || prop.primary) {
553
- return true;
694
+ return !fields?.includes('*');
554
695
  }
555
- return fields.includes(prop.name);
696
+ return fields.some(f => f === prop.name || f.toString().startsWith(prop.name + '.'));
556
697
  };
557
698
  // alias all fields in the primary table
558
699
  meta.props
@@ -579,24 +720,25 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
579
720
  * @internal
580
721
  */
581
722
  mapPropToFieldNames(qb, prop, tableAlias) {
582
- const aliased = qb.ref(tableAlias ? `${tableAlias}__${prop.fieldNames[0]}` : prop.fieldNames[0]).toString();
723
+ const knex = this.connection.getKnex();
724
+ const aliased = knex.ref(tableAlias ? `${tableAlias}__${prop.fieldNames[0]}` : prop.fieldNames[0]).toString();
583
725
  if (prop.customType?.convertToJSValueSQL && tableAlias) {
584
- const prefixed = qb.ref(prop.fieldNames[0]).withSchema(tableAlias).toString();
585
- return [qb.raw(prop.customType.convertToJSValueSQL(prefixed, this.platform) + ' as ' + aliased).toString()];
726
+ const prefixed = knex.ref(prop.fieldNames[0]).withSchema(tableAlias).toString();
727
+ return [(0, core_1.raw)(`${prop.customType.convertToJSValueSQL(prefixed, this.platform)} as ${aliased}`)];
586
728
  }
587
729
  if (prop.formula) {
588
- const alias = qb.ref(tableAlias ?? qb.alias).toString();
589
- return [`${prop.formula(alias)} as ${aliased}`];
730
+ const alias = knex.ref(tableAlias ?? qb.alias).toString();
731
+ return [(0, core_1.raw)(`${prop.formula(alias)} as ${aliased}`)];
590
732
  }
591
733
  if (tableAlias) {
592
- return prop.fieldNames.map(fieldName => qb.ref(fieldName).withSchema(tableAlias).as(`${tableAlias}__${fieldName}`));
734
+ return prop.fieldNames.map(fieldName => knex.ref(fieldName).withSchema(tableAlias).as(`${tableAlias}__${fieldName}`));
593
735
  }
594
736
  return prop.fieldNames;
595
737
  }
596
738
  /** @internal */
597
- createQueryBuilder(entityName, ctx, preferredConnectionType, convertCustomTypes) {
739
+ createQueryBuilder(entityName, ctx, preferredConnectionType, convertCustomTypes, logging) {
598
740
  const connectionType = this.resolveConnectionType({ ctx, connectionType: preferredConnectionType });
599
- const qb = new query_1.QueryBuilder(entityName, this.metadata, this, ctx, undefined, connectionType);
741
+ const qb = new query_1.QueryBuilder(entityName, this.metadata, this, ctx, undefined, connectionType, undefined, logging);
600
742
  if (!convertCustomTypes) {
601
743
  qb.unsetFlag(core_1.QueryFlag.CONVERT_CUSTOM_TYPES);
602
744
  }
@@ -620,7 +762,7 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
620
762
  }
621
763
  const ret = {};
622
764
  this.metadata.find(entityName).relations.forEach(prop => {
623
- if (prop.reference === core_1.ReferenceType.MANY_TO_MANY && data[prop.name]) {
765
+ if (prop.kind === core_1.ReferenceKind.MANY_TO_MANY && data[prop.name]) {
624
766
  ret[prop.name] = data[prop.name].map((item) => core_1.Utils.asArray(item));
625
767
  delete data[prop.name];
626
768
  }
@@ -641,10 +783,9 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
641
783
  if (!deleteDiff) {
642
784
  deleteDiff = [];
643
785
  }
786
+ const pivotMeta = this.metadata.find(prop.pivotEntity);
644
787
  if (deleteDiff === true || deleteDiff.length > 0) {
645
- const qb1 = this.createQueryBuilder(prop.pivotEntity, options?.ctx, 'write')
646
- .withSchema(this.getSchemaName(meta, options))
647
- .unsetFlag(core_1.QueryFlag.CONVERT_CUSTOM_TYPES);
788
+ const qb1 = this.createQueryBuilder(prop.pivotEntity, options?.ctx, 'write').withSchema(this.getSchemaName(pivotMeta, options));
648
789
  const knex = qb1.getKnex();
649
790
  if (Array.isArray(deleteDiff)) {
650
791
  knex.whereIn(prop.inverseJoinColumns, deleteDiff);
@@ -663,13 +804,16 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
663
804
  });
664
805
  /* istanbul ignore else */
665
806
  if (this.platform.allowsMultiInsert()) {
666
- await this.nativeInsertMany(prop.pivotEntity, items, { ...options, convertCustomTypes: false, processCollections: false });
807
+ await this.nativeInsertMany(prop.pivotEntity, items, {
808
+ ...options,
809
+ convertCustomTypes: false,
810
+ processCollections: false,
811
+ });
667
812
  }
668
813
  else {
669
814
  await core_1.Utils.runSerial(items, item => {
670
815
  return this.createQueryBuilder(prop.pivotEntity, options?.ctx, 'write')
671
- .unsetFlag(core_1.QueryFlag.CONVERT_CUSTOM_TYPES)
672
- .withSchema(this.getSchemaName(meta, options))
816
+ .withSchema(this.getSchemaName(pivotMeta, options))
673
817
  .insert(item)
674
818
  .execute('run', false);
675
819
  });
@@ -677,9 +821,9 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
677
821
  }
678
822
  async lockPessimistic(entity, options) {
679
823
  const meta = (0, core_1.helper)(entity).__meta;
680
- const qb = this.createQueryBuilder(entity.constructor.name, options.ctx).unsetFlag(core_1.QueryFlag.CONVERT_CUSTOM_TYPES).withSchema(options.schema ?? meta.schema);
824
+ const qb = this.createQueryBuilder(entity.constructor.name, options.ctx).withSchema(options.schema ?? meta.schema);
681
825
  const cond = core_1.Utils.getPrimaryKeyCond(entity, meta.primaryKeys);
682
- qb.select('1').where(cond).setLockMode(options.lockMode, options.lockTableAliases);
826
+ qb.select((0, core_1.raw)('1')).where(cond).setLockMode(options.lockMode, options.lockTableAliases);
683
827
  await this.rethrow(qb.execute());
684
828
  }
685
829
  buildJoinedPropsOrderBy(entityName, qb, meta, populate, parentPath) {
@@ -691,7 +835,7 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
691
835
  const path = `${parentPath ? parentPath : entityName}.${relation.field}`;
692
836
  const propAlias = qb.getAliasForJoinPath(path);
693
837
  if (propOrderBy) {
694
- Object.keys(propOrderBy).forEach(field => {
838
+ core_1.Utils.keys(propOrderBy).forEach(field => {
695
839
  orderBy.push({ [`${propAlias}.${field}`]: propOrderBy[field] });
696
840
  });
697
841
  }
@@ -718,12 +862,12 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
718
862
  return ret;
719
863
  }
720
864
  processField(meta, prop, field, ret, populate, joinedProps, qb) {
721
- if (!prop || (prop.reference === core_1.ReferenceType.ONE_TO_ONE && !prop.owner)) {
865
+ if (!prop || (prop.kind === core_1.ReferenceKind.ONE_TO_ONE && !prop.owner)) {
722
866
  return;
723
867
  }
724
- if (prop.reference === core_1.ReferenceType.EMBEDDED) {
868
+ if (prop.kind === core_1.ReferenceKind.EMBEDDED) {
725
869
  if (prop.object) {
726
- ret.push(prop.fieldNames[0]);
870
+ ret.push(prop.name);
727
871
  return;
728
872
  }
729
873
  const parts = field.split('.');
@@ -735,9 +879,7 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
735
879
  }
736
880
  return;
737
881
  }
738
- if (prop.fieldNames) {
739
- ret.push(prop.fieldNames[0]);
740
- }
882
+ ret.push(prop.name);
741
883
  }
742
884
  buildFields(meta, populate, joinedProps, qb, fields) {
743
885
  const lazyProps = meta.props.filter(prop => prop.lazy && !populate.some(p => p.field === prop.name || p.all));
@@ -745,6 +887,7 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
745
887
  const requiresSQLConversion = meta.props.some(p => p.customType?.convertToJSValueSQL);
746
888
  const hasExplicitFields = !!fields;
747
889
  const ret = [];
890
+ let addFormulas = false;
748
891
  if (joinedProps.length > 0) {
749
892
  ret.push(...this.getFieldsForJoinedLoad(qb, meta, fields, populate));
750
893
  }
@@ -770,20 +913,22 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
770
913
  else if (lazyProps.filter(p => !p.formula).length > 0) {
771
914
  const props = meta.props.filter(prop => this.platform.shouldHaveColumn(prop, populate, false));
772
915
  ret.push(...core_1.Utils.flatten(props.filter(p => !lazyProps.includes(p)).map(p => p.fieldNames)));
916
+ addFormulas = true;
773
917
  }
774
918
  else if (hasLazyFormulas || requiresSQLConversion) {
775
919
  ret.push('*');
920
+ addFormulas = true;
776
921
  }
777
- if (ret.length > 0 && !hasExplicitFields) {
922
+ if (ret.length > 0 && !hasExplicitFields && addFormulas) {
778
923
  meta.props
779
924
  .filter(prop => prop.formula && !lazyProps.includes(prop))
780
925
  .forEach(prop => {
781
- const alias = qb.ref(qb.alias).toString();
782
- const aliased = qb.ref(prop.fieldNames[0]).toString();
783
- ret.push(`${prop.formula(alias)} as ${aliased}`);
926
+ const alias = this.connection.getKnex().ref(qb.alias).toString();
927
+ const aliased = this.connection.getKnex().ref(prop.fieldNames[0]).toString();
928
+ ret.push((0, core_1.raw)(`${prop.formula(alias)} as ${aliased}`));
784
929
  });
785
930
  meta.props
786
- .filter(prop => prop.customType?.convertToDatabaseValueSQL || prop.customType?.convertToJSValueSQL)
931
+ .filter(prop => prop.hasConvertToDatabaseValueSQL || prop.hasConvertToJSValueSQL)
787
932
  .forEach(prop => ret.push(prop.name));
788
933
  }
789
934
  return ret.length > 0 ? core_1.Utils.unique(ret) : ['*'];