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

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 +283 -118
  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 +55 -30
  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
@@ -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,58 +79,57 @@ 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);
68
86
  return res[0] || null;
69
87
  }
70
88
  async findVirtual(entityName, where, options) {
71
- const meta = this.metadata.get(entityName);
72
- /* istanbul ignore next */
73
- if (!meta.expression) {
74
- return [];
75
- }
76
- if (typeof meta.expression === 'string') {
77
- return this.wrapVirtualExpressionInSubquery(meta, meta.expression, where, options);
78
- }
79
- const em = this.createEntityManager(false);
80
- em.setTransactionContext(options.ctx);
81
- const res = meta.expression(em, where, options);
82
- if (res instanceof (query_1.QueryBuilder)) {
83
- return this.wrapVirtualExpressionInSubquery(meta, res.getFormattedQuery(), where, options);
84
- }
85
- /* istanbul ignore next */
86
- return res;
89
+ return this.findFromVirtual(entityName, where, options, query_1.QueryType.SELECT);
87
90
  }
88
91
  async countVirtual(entityName, where, options) {
92
+ return this.findFromVirtual(entityName, where, options, query_1.QueryType.COUNT);
93
+ }
94
+ async findFromVirtual(entityName, where, options, type) {
89
95
  const meta = this.metadata.get(entityName);
90
96
  /* istanbul ignore next */
91
97
  if (!meta.expression) {
92
- return 0;
98
+ return type === query_1.QueryType.SELECT ? [] : 0;
93
99
  }
94
100
  if (typeof meta.expression === 'string') {
95
- return this.wrapVirtualExpressionInSubquery(meta, meta.expression, where, options, query_1.QueryType.COUNT);
101
+ return this.wrapVirtualExpressionInSubquery(meta, meta.expression, where, options, type);
96
102
  }
97
103
  const em = this.createEntityManager(false);
98
104
  em.setTransactionContext(options.ctx);
99
105
  const res = meta.expression(em, where, options);
100
- if (res instanceof (query_1.QueryBuilder)) {
101
- return this.wrapVirtualExpressionInSubquery(meta, res.getFormattedQuery(), where, options, query_1.QueryType.COUNT);
106
+ if (typeof res === 'string') {
107
+ return this.wrapVirtualExpressionInSubquery(meta, res, where, options, type);
108
+ }
109
+ if (res instanceof query_1.QueryBuilder) {
110
+ return this.wrapVirtualExpressionInSubquery(meta, res.getFormattedQuery(), where, options, type);
111
+ }
112
+ if (core_1.Utils.isObject(res)) {
113
+ const { sql, bindings } = res.toSQL();
114
+ const query = this.platform.formatQuery(sql, bindings);
115
+ return this.wrapVirtualExpressionInSubquery(meta, query, where, options, type);
102
116
  }
103
117
  /* istanbul ignore next */
104
118
  return res;
105
119
  }
106
- async wrapVirtualExpressionInSubquery(meta, expression, where, options, type = query_1.QueryType.SELECT) {
120
+ async wrapVirtualExpressionInSubquery(meta, expression, where, options, type) {
107
121
  const qb = this.createQueryBuilder(meta.className, options?.ctx, options.connectionType, options.convertCustomTypes)
108
- .limit(options?.limit, options?.offset);
122
+ .limit(options?.limit, options?.offset)
123
+ .indexHint(options.indexHint)
124
+ .comment(options.comments)
125
+ .hintComment(options.hintComments);
109
126
  if (options.orderBy) {
110
127
  qb.orderBy(options.orderBy);
111
128
  }
112
129
  qb.where(where);
113
130
  const kqb = qb.getKnexQuery().clear('select');
114
131
  if (type === query_1.QueryType.COUNT) {
115
- kqb.select(qb.raw('count(*) as count'));
132
+ kqb.select(this.connection.getKnex().raw('count(*) as count'));
116
133
  }
117
134
  else { // select
118
135
  kqb.select('*');
@@ -131,7 +148,10 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
131
148
  return null;
132
149
  }
133
150
  if (qb) {
151
+ // here we map the aliased results (cartesian product) to an object graph
134
152
  this.mapJoinedProps(ret, meta, populate, qb, ret, map);
153
+ // we need to remove the cycles from the mapped values
154
+ core_1.Utils.removeCycles(ret, meta);
135
155
  }
136
156
  return ret;
137
157
  }
@@ -146,19 +166,33 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
146
166
  const meta2 = this.metadata.find(relation.type);
147
167
  const path = parentJoinPath ? `${parentJoinPath}.${relation.name}` : `${meta.name}.${relation.name}`;
148
168
  const relationAlias = qb.getAliasForJoinPath(path);
149
- const relationPojo = {};
169
+ let relationPojo = {};
150
170
  // If the primary key value for the relation is null, we know we haven't joined to anything
151
171
  // and therefore we don't return any record (since all values would be null)
152
172
  const hasPK = meta2.primaryKeys.every(pk => meta2.properties[pk].fieldNames.every(name => {
153
173
  return root[`${relationAlias}__${name}`] != null;
154
174
  }));
155
175
  if (!hasPK) {
156
- // 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] || [];
176
+ if ([core_1.ReferenceKind.MANY_TO_MANY, core_1.ReferenceKind.ONE_TO_MANY].includes(relation.kind)) {
177
+ result[relation.name] ??= [];
178
+ }
179
+ if ([core_1.ReferenceKind.MANY_TO_ONE, core_1.ReferenceKind.ONE_TO_ONE].includes(relation.kind)) {
180
+ result[relation.name] = null;
159
181
  }
160
182
  return;
161
183
  }
184
+ const tz = this.platform.getTimezone();
185
+ meta2.props
186
+ .filter(prop => prop.persist === false && prop.fieldNames)
187
+ .forEach(prop => {
188
+ if (prop.fieldNames.length > 1) { // composite keys
189
+ relationPojo[prop.name] = prop.fieldNames.map(name => root[`${relationAlias}__${name}`]);
190
+ }
191
+ else {
192
+ const alias = `${relationAlias}__${prop.fieldNames[0]}`;
193
+ relationPojo[prop.name] = root[alias];
194
+ }
195
+ });
162
196
  meta2.props
163
197
  .filter(prop => this.platform.shouldHaveColumn(prop, p.children || []))
164
198
  .forEach(prop => {
@@ -166,21 +200,37 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
166
200
  relationPojo[prop.name] = prop.fieldNames.map(name => root[`${relationAlias}__${name}`]);
167
201
  prop.fieldNames.map(name => delete root[`${relationAlias}__${name}`]);
168
202
  }
203
+ else if (prop.runtimeType === 'Date') {
204
+ const alias = `${relationAlias}__${prop.fieldNames[0]}`;
205
+ relationPojo[prop.name] = (typeof root[alias] === 'string' ? new Date(root[alias]) : root[alias]);
206
+ delete root[alias];
207
+ }
169
208
  else {
170
209
  const alias = `${relationAlias}__${prop.fieldNames[0]}`;
171
210
  relationPojo[prop.name] = root[alias];
172
211
  delete root[alias];
173
212
  }
174
213
  });
175
- const key = `${meta.name}-${(core_1.Utils.getCompositeKeyHash(result, meta))}`;
214
+ const key = `${meta.name}-${core_1.Utils.getCompositeKeyHash(result, meta)}`;
215
+ const key2 = `${meta2.name}-${core_1.Utils.getCompositeKeyHash(relationPojo, meta2)}`;
216
+ if (map[key2]) {
217
+ const old = map[key2];
218
+ core_1.Utils.keys(relationPojo)
219
+ .filter(k => relationPojo[k] != null)
220
+ .forEach(k => old[k] = relationPojo[k]);
221
+ relationPojo = old;
222
+ }
223
+ else {
224
+ map[key2] = relationPojo;
225
+ }
176
226
  if (map[key]) {
177
227
  result[relation.name] = map[key][relation.name];
178
228
  }
179
229
  else {
180
230
  map[key] = result;
181
231
  }
182
- if ([core_1.ReferenceType.MANY_TO_MANY, core_1.ReferenceType.ONE_TO_MANY].includes(relation.reference)) {
183
- result[relation.name] = result[relation.name] || [];
232
+ if ([core_1.ReferenceKind.MANY_TO_MANY, core_1.ReferenceKind.ONE_TO_MANY].includes(relation.kind)) {
233
+ result[relation.name] ??= [];
184
234
  this.appendToCollection(meta2, result[relation.name], relationPojo);
185
235
  }
186
236
  else {
@@ -207,6 +257,9 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
207
257
  return this.countVirtual(entityName, where, options);
208
258
  }
209
259
  const qb = this.createQueryBuilder(entityName, options.ctx, options.connectionType, false)
260
+ .indexHint(options.indexHint)
261
+ .comment(options.comments)
262
+ .hintComment(options.hintComments)
210
263
  .groupBy(options.groupBy)
211
264
  .having(options.having)
212
265
  .populate(options.populate ?? [])
@@ -215,7 +268,7 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
215
268
  return this.rethrow(qb.getCount());
216
269
  }
217
270
  async nativeInsert(entityName, data, options = {}) {
218
- options.convertCustomTypes ?? (options.convertCustomTypes = true);
271
+ options.convertCustomTypes ??= true;
219
272
  const meta = this.metadata.find(entityName);
220
273
  const collections = this.extractManyToMany(entityName, data);
221
274
  const pks = meta?.primaryKeys ?? [this.config.getNamingStrategy().referenceColumnName()];
@@ -235,16 +288,20 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
235
288
  return res;
236
289
  }
237
290
  async nativeInsertMany(entityName, data, options = {}) {
238
- options.processCollections ?? (options.processCollections = true);
239
- options.convertCustomTypes ?? (options.convertCustomTypes = true);
240
- const meta = this.metadata.find(entityName);
291
+ options.processCollections ??= true;
292
+ options.convertCustomTypes ??= true;
293
+ const meta = this.metadata.find(entityName)?.root;
241
294
  const collections = options.processCollections ? data.map(d => this.extractManyToMany(entityName, d)) : [];
242
295
  const pks = this.getPrimaryKeyFields(entityName);
243
296
  const set = new Set();
244
- data.forEach(row => Object.keys(row).forEach(k => set.add(k)));
297
+ data.forEach(row => core_1.Utils.keys(row).forEach(k => set.add(k)));
245
298
  const props = [...set].map(name => meta?.properties[name] ?? { name, fieldNames: [name] });
246
- const fields = core_1.Utils.flatten(props.map(prop => prop.fieldNames));
299
+ let fields = core_1.Utils.flatten(props.map(prop => prop.fieldNames));
300
+ const duplicates = core_1.Utils.findDuplicates(fields);
247
301
  const params = [];
302
+ if (duplicates.length) {
303
+ fields = core_1.Utils.unique(fields);
304
+ }
248
305
  /* istanbul ignore next */
249
306
  const tableName = meta ? this.getTableName(meta, options) : this.platform.quoteIdentifier(entityName);
250
307
  let sql = `insert into ${tableName} `;
@@ -255,29 +312,50 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
255
312
  else {
256
313
  sql += ' ' + data.map(() => `select null as ${this.platform.quoteIdentifier(pks[0])}`).join(' union all ');
257
314
  }
315
+ const addParams = (prop, row) => {
316
+ if (options.convertCustomTypes && prop.customType) {
317
+ params.push(prop.customType.convertToDatabaseValue(row[prop.name], this.platform, { key: prop.name, mode: 'query-data' }));
318
+ return;
319
+ }
320
+ params.push(row[prop.name]);
321
+ };
258
322
  if (fields.length > 0 || this.platform.usesDefaultKeyword()) {
259
323
  sql += data.map(row => {
260
324
  const keys = [];
325
+ const usedDups = [];
261
326
  props.forEach(prop => {
262
327
  if (prop.fieldNames.length > 1) {
263
- params.push(...(row[prop.name] ?? prop.fieldNames.map(() => null)));
264
- keys.push(...prop.fieldNames.map(_ => '?'));
265
- }
266
- else if (prop.customType && 'convertToDatabaseValueSQL' in prop.customType && !this.platform.isRaw(row[prop.name])) {
267
- keys.push(prop.customType.convertToDatabaseValueSQL('?', this.platform));
268
- params.push(row[prop.name]);
328
+ const param = core_1.Utils.flatten([...row[prop.name] ?? prop.fieldNames.map(() => null)]);
329
+ const key = param.map(() => '?');
330
+ prop.fieldNames.forEach((field, idx) => {
331
+ if (!duplicates.includes(field) || !usedDups.includes(field)) {
332
+ params.push(param[idx]);
333
+ keys.push(key[idx]);
334
+ usedDups.push(field);
335
+ }
336
+ });
269
337
  }
270
338
  else {
271
- params.push(row[prop.name]);
272
- keys.push('?');
339
+ const field = prop.fieldNames[0];
340
+ if (!duplicates.includes(field) || !usedDups.includes(field)) {
341
+ if (prop.customType && 'convertToDatabaseValueSQL' in prop.customType && !this.platform.isRaw(row[prop.name])) {
342
+ keys.push(prop.customType.convertToDatabaseValueSQL('?', this.platform));
343
+ }
344
+ else {
345
+ keys.push('?');
346
+ }
347
+ addParams(prop, row);
348
+ usedDups.push(field);
349
+ }
273
350
  }
274
351
  });
275
352
  return '(' + (keys.join(', ') || 'default') + ')';
276
353
  }).join(', ');
277
354
  }
278
- if (this.platform.usesReturningStatement()) {
279
- /* istanbul ignore next */
280
- const returningProps = meta.hydrateProps.filter(prop => prop.persist !== false && (prop.primary || prop.defaultRaw));
355
+ if (meta && this.platform.usesReturningStatement()) {
356
+ const returningProps = meta.props
357
+ .filter(prop => prop.persist !== false && ((prop.primary && prop.autoincrement) || prop.defaultRaw))
358
+ .filter(prop => !(prop.name in data[0]) || core_1.Utils.isRawSql(data[0][prop.name]));
281
359
  const returningFields = core_1.Utils.flatten(returningProps.map(prop => prop.fieldNames));
282
360
  /* istanbul ignore next */
283
361
  sql += returningFields.length > 0 ? ` returning ${returningFields.map(field => this.platform.quoteIdentifier(field)).join(', ')}` : '';
@@ -289,8 +367,8 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
289
367
  pk = data.map(d => core_1.Utils.getPrimaryKeyCond(d, pks));
290
368
  }
291
369
  else {
292
- res.row ?? (res.row = {});
293
- res.rows ?? (res.rows = []);
370
+ res.row ??= {};
371
+ res.rows ??= [];
294
372
  pk = data.map((d, i) => d[pks[0]] ?? res.rows[i]?.[pks[0]]).map(d => [d]);
295
373
  res.insertId = res.insertId || res.row[pks[0]];
296
374
  }
@@ -300,7 +378,7 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
300
378
  return res;
301
379
  }
302
380
  async nativeUpdate(entityName, where, data, options = {}) {
303
- options.convertCustomTypes ?? (options.convertCustomTypes = true);
381
+ options.convertCustomTypes ??= true;
304
382
  const meta = this.metadata.find(entityName);
305
383
  const pks = this.getPrimaryKeyFields(entityName);
306
384
  const collections = this.extractManyToMany(entityName, data);
@@ -314,11 +392,18 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
314
392
  .withSchema(this.getSchemaName(meta, options));
315
393
  if (options.upsert) {
316
394
  /* istanbul ignore next */
317
- const uniqueFields = core_1.Utils.isPlainObject(where) ? Object.keys(where) : meta.primaryKeys;
318
- /* istanbul ignore next */
395
+ const uniqueFields = options.onConflictFields ?? (core_1.Utils.isPlainObject(where) ? core_1.Utils.keys(where) : meta.primaryKeys);
396
+ const returning = (0, core_1.getOnConflictReturningFields)(meta, data, uniqueFields, options);
319
397
  qb.insert(data)
320
- .onConflict(uniqueFields.map(p => meta?.properties[p]?.fieldNames[0] ?? p))
321
- .merge(Object.keys(data).filter(f => !uniqueFields.includes(f)));
398
+ .onConflict(uniqueFields)
399
+ .returning(returning);
400
+ if (!options.onConflictAction || options.onConflictAction === 'merge') {
401
+ const fields = (0, core_1.getOnConflictFields)(data, uniqueFields, options);
402
+ qb.merge(fields);
403
+ }
404
+ if (options.onConflictAction === 'ignore') {
405
+ qb.ignore();
406
+ }
322
407
  }
323
408
  else {
324
409
  qb.update(data).where(where);
@@ -331,20 +416,36 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
331
416
  return res;
332
417
  }
333
418
  async nativeUpdateMany(entityName, where, data, options = {}) {
334
- options.processCollections ?? (options.processCollections = true);
335
- options.convertCustomTypes ?? (options.convertCustomTypes = true);
419
+ options.processCollections ??= true;
420
+ options.convertCustomTypes ??= true;
336
421
  const meta = this.metadata.get(entityName);
337
422
  if (options.upsert) {
338
- const uniqueFields = core_1.Utils.isPlainObject(where[0]) ? Object.keys(where[0]) : meta.primaryKeys;
423
+ const uniqueFields = options.onConflictFields ?? (core_1.Utils.isPlainObject(where[0]) ? Object.keys(where[0]) : meta.primaryKeys);
339
424
  const qb = this.createQueryBuilder(entityName, options.ctx, 'write', options.convertCustomTypes).withSchema(this.getSchemaName(meta, options));
425
+ const returning = (0, core_1.getOnConflictReturningFields)(meta, data[0], uniqueFields, options);
340
426
  qb.insert(data)
341
- .onConflict(uniqueFields.map(p => meta?.properties[p]?.fieldNames[0] ?? p))
342
- .merge(Object.keys(data[0]).filter(f => !uniqueFields.includes(f)));
427
+ .onConflict(uniqueFields)
428
+ .returning(returning);
429
+ if (!options.onConflictAction || options.onConflictAction === 'merge') {
430
+ const fields = (0, core_1.getOnConflictFields)(data[0], uniqueFields, options);
431
+ qb.merge(fields);
432
+ }
433
+ if (options.onConflictAction === 'ignore') {
434
+ qb.ignore();
435
+ }
343
436
  return qb.execute('run', false);
344
437
  }
345
438
  const collections = options.processCollections ? data.map(d => this.extractManyToMany(entityName, d)) : [];
346
439
  const keys = new Set();
347
- data.forEach(row => Object.keys(row).forEach(k => keys.add(k)));
440
+ const returning = new Set();
441
+ data.forEach(row => {
442
+ core_1.Utils.keys(row).forEach(k => {
443
+ keys.add(k);
444
+ if (core_1.Utils.isRawSql(row[k])) {
445
+ returning.add(k);
446
+ }
447
+ });
448
+ });
348
449
  const pkCond = core_1.Utils.flatten(meta.primaryKeys.map(pk => meta.properties[pk].fieldNames)).map(pk => `${this.platform.quoteIdentifier(pk)} = ?`).join(' and ');
349
450
  const params = [];
350
451
  let sql = `update ${this.getTableName(meta, options)} set `;
@@ -373,7 +474,7 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
373
474
  const versionProperty = meta.properties[meta.versionProperty];
374
475
  const quotedFieldName = this.platform.quoteIdentifier(versionProperty.fieldNames[0]);
375
476
  sql += `${quotedFieldName} = `;
376
- if (versionProperty.type.toLowerCase() === 'date') {
477
+ if (versionProperty.runtimeType === 'Date') {
377
478
  sql += this.platform.getCurrentTimestampSQL(versionProperty.length);
378
479
  }
379
480
  else {
@@ -404,6 +505,11 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
404
505
  return '?';
405
506
  });
406
507
  sql += ` in (${conds.join(', ')})`;
508
+ if (this.platform.usesReturningStatement() && returning.size > 0) {
509
+ const returningFields = core_1.Utils.flatten([...returning].map(prop => meta.properties[prop].fieldNames));
510
+ /* istanbul ignore next */
511
+ sql += returningFields.length > 0 ? ` returning ${returningFields.map(field => this.platform.quoteIdentifier(field)).join(', ')}` : '';
512
+ }
407
513
  const res = await this.rethrow(this.execute(sql, params, 'run', options.ctx));
408
514
  for (let i = 0; i < collections.length; i++) {
409
515
  await this.processManyToMany(meta, where[i], collections[i], false, options);
@@ -438,30 +544,53 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
438
544
  deleteDiff.push(...snapshot);
439
545
  insertDiff.push(...current);
440
546
  }
441
- if (coll.property.reference === core_1.ReferenceType.ONE_TO_MANY) {
547
+ if (coll.property.kind === core_1.ReferenceKind.ONE_TO_MANY) {
442
548
  const cols = coll.property.referencedColumnNames;
443
549
  const qb = this.createQueryBuilder(coll.property.type, ctx, 'write')
444
- .withSchema(this.getSchemaName(meta, options))
445
- .update({ [coll.property.mappedBy]: pks })
550
+ .withSchema(this.getSchemaName(meta, options));
551
+ if (coll.getSnapshot() === undefined) {
552
+ if (coll.property.orphanRemoval) {
553
+ const kqb = qb.delete({ [coll.property.mappedBy]: pks })
554
+ .getKnexQuery()
555
+ .whereNotIn(cols, insertDiff);
556
+ return this.rethrow(this.execute(kqb));
557
+ }
558
+ const kqb = qb.update({ [coll.property.mappedBy]: null })
559
+ .getKnexQuery()
560
+ .whereNotIn(cols, insertDiff);
561
+ return this.rethrow(this.execute(kqb));
562
+ }
563
+ const kqb = qb.update({ [coll.property.mappedBy]: pks })
446
564
  .getKnexQuery()
447
565
  .whereIn(cols, insertDiff);
448
- return this.rethrow(this.execute(qb));
566
+ return this.rethrow(this.execute(kqb));
449
567
  }
450
568
  /* istanbul ignore next */
451
569
  const ownerSchema = wrapped.getSchema() === '*' ? this.config.get('schema') : wrapped.getSchema();
452
570
  const pivotMeta = this.metadata.find(coll.property.pivotEntity);
453
571
  if (pivotMeta.schema === '*') {
454
572
  /* istanbul ignore next */
455
- options ?? (options = {});
573
+ options ??= {};
456
574
  options.schema = ownerSchema;
457
575
  }
458
576
  return this.rethrow(this.updateCollectionDiff(meta, coll.property, pks, deleteDiff, insertDiff, options));
459
577
  }
460
578
  async loadFromPivotTable(prop, owners, where = {}, orderBy, ctx, options) {
461
579
  const pivotProp2 = this.getPivotInverseProperty(prop);
580
+ const prop2 = prop.targetMeta.properties[prop.mappedBy ?? prop.inversedBy];
462
581
  const ownerMeta = this.metadata.find(pivotProp2.type);
463
582
  const pivotMeta = this.metadata.find(prop.pivotEntity);
464
- const cond = { [`${prop.pivotEntity}.${pivotProp2.name}`]: { $in: ownerMeta.compositePK ? owners : owners.map(o => o[0]) } };
583
+ options = { ...options };
584
+ const qb = this.createQueryBuilder(prop.type, ctx, options.connectionType)
585
+ .withSchema(this.getSchemaName(prop.targetMeta, options))
586
+ .indexHint(options.indexHint)
587
+ .comment(options.comments)
588
+ .hintComment(options.hintComments);
589
+ const pivotAlias = qb.getNextAlias(pivotMeta.tableName);
590
+ const pivotKey = pivotProp2.joinColumns.map(column => `${pivotAlias}.${column}`).join(core_1.Utils.PK_SEPARATOR);
591
+ const cond = {
592
+ [pivotKey]: { $in: ownerMeta.compositePK ? owners : owners.map(o => o[0]) },
593
+ };
465
594
  /* istanbul ignore if */
466
595
  if (!core_1.Utils.isEmpty(where) && Object.keys(where).every(k => core_1.Utils.isOperator(k, false))) {
467
596
  where = cond;
@@ -469,22 +598,32 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
469
598
  else {
470
599
  where = { ...where, ...cond };
471
600
  }
472
- orderBy = this.getPivotOrderBy(prop, orderBy);
473
- const qb = this.createQueryBuilder(prop.type, ctx, options?.connectionType)
474
- .unsetFlag(core_1.QueryFlag.CONVERT_CUSTOM_TYPES)
475
- .withSchema(this.getSchemaName(prop.targetMeta, options));
476
- const populate = this.autoJoinOneToOneOwner(prop.targetMeta, [{ field: prop.pivotEntity }]);
477
- const fields = this.buildFields(prop.targetMeta, (options?.populate ?? []), [], qb, options?.fields);
478
- qb.select(fields).populate(populate).where(where).orderBy(orderBy).setLockMode(options?.lockMode, options?.lockTableAliases);
479
- if (owners.length === 1 && (options?.offset != null || options?.limit != null)) {
601
+ orderBy = this.getPivotOrderBy(prop, pivotAlias, orderBy);
602
+ const populate = this.autoJoinOneToOneOwner(prop.targetMeta, []);
603
+ const fields = this.buildFields(prop.targetMeta, (options.populate ?? []), [], qb, options.fields);
604
+ const k1 = !prop.owner ? 'joinColumns' : 'inverseJoinColumns';
605
+ const k2 = prop.owner ? 'joinColumns' : 'inverseJoinColumns';
606
+ const cols = [
607
+ ...prop[k1].map(col => `${pivotAlias}.${col} as fk__${col}`),
608
+ ...prop[k2].map(col => `${pivotAlias}.${col} as fk__${col}`),
609
+ ];
610
+ fields.push(...cols);
611
+ qb.select(fields)
612
+ .join(prop2.name, pivotAlias, {}, 'pivotJoin', undefined, options.schema)
613
+ .populate(populate)
614
+ .where(where)
615
+ .orderBy(orderBy)
616
+ .setLockMode(options.lockMode, options.lockTableAliases);
617
+ if (owners.length === 1 && (options.offset != null || options.limit != null)) {
480
618
  qb.limit(options.limit, options.offset);
481
619
  }
482
- if (prop.targetMeta.schema !== '*' && pivotMeta.schema === '*' && options?.schema) {
620
+ const schema = this.config.get('schema');
621
+ if (prop.targetMeta.schema !== '*' && pivotMeta.schema === '*' && options.schema) {
483
622
  // eslint-disable-next-line dot-notation
484
623
  qb['finalize']();
485
624
  // eslint-disable-next-line dot-notation
486
625
  Object.values(qb['_joins']).forEach(join => {
487
- join.schema = options.schema;
626
+ join.schema = schema;
488
627
  });
489
628
  }
490
629
  const items = owners.length ? await this.rethrow(qb.execute('all')) : [];
@@ -508,6 +647,18 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
508
647
  });
509
648
  return map;
510
649
  }
650
+ getPivotOrderBy(prop, pivotAlias, orderBy) {
651
+ if (!core_1.Utils.isEmpty(orderBy)) {
652
+ return orderBy;
653
+ }
654
+ if (!core_1.Utils.isEmpty(prop.orderBy)) {
655
+ return core_1.Utils.asArray(prop.orderBy);
656
+ }
657
+ if (prop.fixedOrder) {
658
+ return [{ [`${pivotAlias}.${prop.fixedOrderColumn}`]: core_1.QueryOrder.ASC }];
659
+ }
660
+ return [];
661
+ }
511
662
  async execute(queryOrKnex, params = [], method = 'all', ctx) {
512
663
  return this.rethrow(this.connection.execute(queryOrKnex, params, method, ctx));
513
664
  }
@@ -520,7 +671,7 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
520
671
  }
521
672
  const relationsToPopulate = populate.map(({ field }) => field);
522
673
  const toPopulate = meta.relations
523
- .filter(prop => prop.reference === core_1.ReferenceType.ONE_TO_ONE && !prop.owner && !relationsToPopulate.includes(prop.name))
674
+ .filter(prop => prop.kind === core_1.ReferenceKind.ONE_TO_ONE && !prop.owner && !relationsToPopulate.includes(prop.name))
524
675
  .filter(prop => fields.length === 0 || fields.some(f => prop.name === f || prop.name.startsWith(`${String(f)}.`)))
525
676
  .map(prop => ({ field: prop.name, strategy: prop.strategy }));
526
677
  return [...populate, ...toPopulate];
@@ -528,23 +679,30 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
528
679
  joinedProps(meta, populate) {
529
680
  return populate.filter(p => {
530
681
  const prop = meta.properties[p.field] || {};
531
- return (p.strategy || prop.strategy || this.config.get('loadStrategy')) === core_1.LoadStrategy.JOINED && prop.reference !== core_1.ReferenceType.SCALAR;
682
+ return (p.strategy || prop.strategy || this.config.get('loadStrategy')) === core_1.LoadStrategy.JOINED && prop.kind !== core_1.ReferenceKind.SCALAR;
532
683
  });
533
684
  }
534
685
  /**
535
686
  * @internal
536
687
  */
537
- mergeJoinedResult(rawResults, meta) {
688
+ mergeJoinedResult(rawResults, meta, joinedProps) {
538
689
  // group by the root entity primary key first
539
690
  const map = {};
540
691
  const res = [];
541
692
  rawResults.forEach(item => {
542
693
  const pk = core_1.Utils.getCompositeKeyHash(item, meta);
543
694
  if (map[pk]) {
544
- map[pk].push(item);
695
+ joinedProps.forEach(hint => {
696
+ // Sometimes we might join a M:N relation with additional filter on the target entity, and as a result, we get
697
+ // the first result with `null` for all target values, which is mapped as empty array. When we see that happen,
698
+ // we need to merge the results of the next item.
699
+ if (Array.isArray(map[pk][hint.field]) && Array.isArray(item[hint.field]) && map[pk][hint.field].length === 0) {
700
+ item[hint.field].forEach((el) => map[pk][hint.field].push(el));
701
+ }
702
+ });
545
703
  }
546
704
  else {
547
- map[pk] = [item];
705
+ map[pk] = item;
548
706
  res.push(item);
549
707
  }
550
708
  });
@@ -553,14 +711,17 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
553
711
  getFieldsForJoinedLoad(qb, meta, explicitFields, populate = [], parentTableAlias, parentJoinPath) {
554
712
  const fields = [];
555
713
  const joinedProps = this.joinedProps(meta, populate);
714
+ if (explicitFields?.includes('*')) {
715
+ fields.push('*');
716
+ }
556
717
  const shouldHaveColumn = (prop, populate, fields) => {
557
718
  if (!this.platform.shouldHaveColumn(prop, populate)) {
558
719
  return false;
559
720
  }
560
721
  if (!fields || prop.primary) {
561
- return true;
722
+ return !fields?.includes('*');
562
723
  }
563
- return fields.includes(prop.name);
724
+ return fields.some(f => f === prop.name || f.toString().startsWith(prop.name + '.'));
564
725
  };
565
726
  // alias all fields in the primary table
566
727
  meta.props
@@ -587,24 +748,25 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
587
748
  * @internal
588
749
  */
589
750
  mapPropToFieldNames(qb, prop, tableAlias) {
590
- const aliased = qb.ref(tableAlias ? `${tableAlias}__${prop.fieldNames[0]}` : prop.fieldNames[0]).toString();
751
+ const knex = this.connection.getKnex();
752
+ const aliased = knex.ref(tableAlias ? `${tableAlias}__${prop.fieldNames[0]}` : prop.fieldNames[0]).toString();
591
753
  if (prop.customType?.convertToJSValueSQL && tableAlias) {
592
- const prefixed = qb.ref(prop.fieldNames[0]).withSchema(tableAlias).toString();
593
- return [qb.raw(prop.customType.convertToJSValueSQL(prefixed, this.platform) + ' as ' + aliased).toString()];
754
+ const prefixed = knex.ref(prop.fieldNames[0]).withSchema(tableAlias).toString();
755
+ return [(0, core_1.raw)(`${prop.customType.convertToJSValueSQL(prefixed, this.platform)} as ${aliased}`)];
594
756
  }
595
757
  if (prop.formula) {
596
- const alias = qb.ref(tableAlias ?? qb.alias).toString();
597
- return [`${prop.formula(alias)} as ${aliased}`];
758
+ const alias = knex.ref(tableAlias ?? qb.alias).toString();
759
+ return [(0, core_1.raw)(`${prop.formula(alias)} as ${aliased}`)];
598
760
  }
599
761
  if (tableAlias) {
600
- return prop.fieldNames.map(fieldName => qb.ref(fieldName).withSchema(tableAlias).as(`${tableAlias}__${fieldName}`));
762
+ return prop.fieldNames.map(fieldName => knex.ref(fieldName).withSchema(tableAlias).as(`${tableAlias}__${fieldName}`));
601
763
  }
602
764
  return prop.fieldNames;
603
765
  }
604
766
  /** @internal */
605
- createQueryBuilder(entityName, ctx, preferredConnectionType, convertCustomTypes) {
767
+ createQueryBuilder(entityName, ctx, preferredConnectionType, convertCustomTypes, logging) {
606
768
  const connectionType = this.resolveConnectionType({ ctx, connectionType: preferredConnectionType });
607
- const qb = new query_1.QueryBuilder(entityName, this.metadata, this, ctx, undefined, connectionType);
769
+ const qb = new query_1.QueryBuilder(entityName, this.metadata, this, ctx, undefined, connectionType, undefined, logging);
608
770
  if (!convertCustomTypes) {
609
771
  qb.unsetFlag(core_1.QueryFlag.CONVERT_CUSTOM_TYPES);
610
772
  }
@@ -628,7 +790,7 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
628
790
  }
629
791
  const ret = {};
630
792
  this.metadata.find(entityName).relations.forEach(prop => {
631
- if (prop.reference === core_1.ReferenceType.MANY_TO_MANY && data[prop.name]) {
793
+ if (prop.kind === core_1.ReferenceKind.MANY_TO_MANY && data[prop.name]) {
632
794
  ret[prop.name] = data[prop.name].map((item) => core_1.Utils.asArray(item));
633
795
  delete data[prop.name];
634
796
  }
@@ -649,10 +811,9 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
649
811
  if (!deleteDiff) {
650
812
  deleteDiff = [];
651
813
  }
814
+ const pivotMeta = this.metadata.find(prop.pivotEntity);
652
815
  if (deleteDiff === true || deleteDiff.length > 0) {
653
- const qb1 = this.createQueryBuilder(prop.pivotEntity, options?.ctx, 'write')
654
- .withSchema(this.getSchemaName(meta, options))
655
- .unsetFlag(core_1.QueryFlag.CONVERT_CUSTOM_TYPES);
816
+ const qb1 = this.createQueryBuilder(prop.pivotEntity, options?.ctx, 'write').withSchema(this.getSchemaName(pivotMeta, options));
656
817
  const knex = qb1.getKnex();
657
818
  if (Array.isArray(deleteDiff)) {
658
819
  knex.whereIn(prop.inverseJoinColumns, deleteDiff);
@@ -671,13 +832,16 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
671
832
  });
672
833
  /* istanbul ignore else */
673
834
  if (this.platform.allowsMultiInsert()) {
674
- await this.nativeInsertMany(prop.pivotEntity, items, { ...options, convertCustomTypes: false, processCollections: false });
835
+ await this.nativeInsertMany(prop.pivotEntity, items, {
836
+ ...options,
837
+ convertCustomTypes: false,
838
+ processCollections: false,
839
+ });
675
840
  }
676
841
  else {
677
842
  await core_1.Utils.runSerial(items, item => {
678
843
  return this.createQueryBuilder(prop.pivotEntity, options?.ctx, 'write')
679
- .unsetFlag(core_1.QueryFlag.CONVERT_CUSTOM_TYPES)
680
- .withSchema(this.getSchemaName(meta, options))
844
+ .withSchema(this.getSchemaName(pivotMeta, options))
681
845
  .insert(item)
682
846
  .execute('run', false);
683
847
  });
@@ -685,9 +849,9 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
685
849
  }
686
850
  async lockPessimistic(entity, options) {
687
851
  const meta = (0, core_1.helper)(entity).__meta;
688
- const qb = this.createQueryBuilder(entity.constructor.name, options.ctx).unsetFlag(core_1.QueryFlag.CONVERT_CUSTOM_TYPES).withSchema(options.schema ?? meta.schema);
852
+ const qb = this.createQueryBuilder(entity.constructor.name, options.ctx).withSchema(options.schema ?? meta.schema);
689
853
  const cond = core_1.Utils.getPrimaryKeyCond(entity, meta.primaryKeys);
690
- qb.select('1').where(cond).setLockMode(options.lockMode, options.lockTableAliases);
854
+ qb.select((0, core_1.raw)('1')).where(cond).setLockMode(options.lockMode, options.lockTableAliases);
691
855
  await this.rethrow(qb.execute());
692
856
  }
693
857
  buildJoinedPropsOrderBy(entityName, qb, meta, populate, parentPath) {
@@ -699,7 +863,7 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
699
863
  const path = `${parentPath ? parentPath : entityName}.${relation.field}`;
700
864
  const propAlias = qb.getAliasForJoinPath(path);
701
865
  if (propOrderBy) {
702
- Object.keys(propOrderBy).forEach(field => {
866
+ core_1.Utils.keys(propOrderBy).forEach(field => {
703
867
  orderBy.push({ [`${propAlias}.${field}`]: propOrderBy[field] });
704
868
  });
705
869
  }
@@ -726,12 +890,12 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
726
890
  return ret;
727
891
  }
728
892
  processField(meta, prop, field, ret, populate, joinedProps, qb) {
729
- if (!prop || (prop.reference === core_1.ReferenceType.ONE_TO_ONE && !prop.owner)) {
893
+ if (!prop || (prop.kind === core_1.ReferenceKind.ONE_TO_ONE && !prop.owner)) {
730
894
  return;
731
895
  }
732
- if (prop.reference === core_1.ReferenceType.EMBEDDED) {
896
+ if (prop.kind === core_1.ReferenceKind.EMBEDDED) {
733
897
  if (prop.object) {
734
- ret.push(prop.fieldNames[0]);
898
+ ret.push(prop.name);
735
899
  return;
736
900
  }
737
901
  const parts = field.split('.');
@@ -743,9 +907,7 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
743
907
  }
744
908
  return;
745
909
  }
746
- if (prop.fieldNames) {
747
- ret.push(prop.fieldNames[0]);
748
- }
910
+ ret.push(prop.name);
749
911
  }
750
912
  buildFields(meta, populate, joinedProps, qb, fields) {
751
913
  const lazyProps = meta.props.filter(prop => prop.lazy && !populate.some(p => p.field === prop.name || p.all));
@@ -753,6 +915,7 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
753
915
  const requiresSQLConversion = meta.props.some(p => p.customType?.convertToJSValueSQL);
754
916
  const hasExplicitFields = !!fields;
755
917
  const ret = [];
918
+ let addFormulas = false;
756
919
  if (joinedProps.length > 0) {
757
920
  ret.push(...this.getFieldsForJoinedLoad(qb, meta, fields, populate));
758
921
  }
@@ -778,20 +941,22 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
778
941
  else if (lazyProps.filter(p => !p.formula).length > 0) {
779
942
  const props = meta.props.filter(prop => this.platform.shouldHaveColumn(prop, populate, false));
780
943
  ret.push(...core_1.Utils.flatten(props.filter(p => !lazyProps.includes(p)).map(p => p.fieldNames)));
944
+ addFormulas = true;
781
945
  }
782
946
  else if (hasLazyFormulas || requiresSQLConversion) {
783
947
  ret.push('*');
948
+ addFormulas = true;
784
949
  }
785
- if (ret.length > 0 && !hasExplicitFields) {
950
+ if (ret.length > 0 && !hasExplicitFields && addFormulas) {
786
951
  meta.props
787
952
  .filter(prop => prop.formula && !lazyProps.includes(prop))
788
953
  .forEach(prop => {
789
- const alias = qb.ref(qb.alias).toString();
790
- const aliased = qb.ref(prop.fieldNames[0]).toString();
791
- ret.push(`${prop.formula(alias)} as ${aliased}`);
954
+ const alias = this.connection.getKnex().ref(qb.alias).toString();
955
+ const aliased = this.connection.getKnex().ref(prop.fieldNames[0]).toString();
956
+ ret.push((0, core_1.raw)(`${prop.formula(alias)} as ${aliased}`));
792
957
  });
793
958
  meta.props
794
- .filter(prop => prop.customType?.convertToDatabaseValueSQL || prop.customType?.convertToJSValueSQL)
959
+ .filter(prop => prop.hasConvertToDatabaseValueSQL || prop.hasConvertToJSValueSQL)
795
960
  .forEach(prop => ret.push(prop.name));
796
961
  }
797
962
  return ret.length > 0 ? core_1.Utils.unique(ret) : ['*'];