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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/AbstractSqlConnection.d.ts +8 -4
  2. package/AbstractSqlConnection.js +30 -18
  3. package/AbstractSqlDriver.d.ts +16 -17
  4. package/AbstractSqlDriver.js +271 -113
  5. package/AbstractSqlPlatform.d.ts +5 -4
  6. package/AbstractSqlPlatform.js +34 -5
  7. package/MonkeyPatchable.d.ts +1 -0
  8. package/MonkeyPatchable.js +3 -0
  9. package/README.md +64 -107
  10. package/SqlEntityManager.d.ts +2 -6
  11. package/SqlEntityManager.js +5 -9
  12. package/SqlEntityRepository.d.ts +7 -6
  13. package/SqlEntityRepository.js +11 -8
  14. package/index.mjs +22 -6
  15. package/package.json +8 -8
  16. package/query/ArrayCriteriaNode.d.ts +3 -3
  17. package/query/CriteriaNode.d.ts +8 -8
  18. package/query/CriteriaNode.js +16 -9
  19. package/query/CriteriaNodeFactory.d.ts +6 -6
  20. package/query/CriteriaNodeFactory.js +10 -13
  21. package/query/ObjectCriteriaNode.d.ts +3 -3
  22. package/query/ObjectCriteriaNode.js +12 -8
  23. package/query/QueryBuilder.d.ts +39 -19
  24. package/query/QueryBuilder.js +331 -111
  25. package/query/QueryBuilderHelper.d.ts +9 -13
  26. package/query/QueryBuilderHelper.js +153 -107
  27. package/query/ScalarCriteriaNode.d.ts +3 -2
  28. package/query/ScalarCriteriaNode.js +9 -6
  29. package/query/enums.js +1 -1
  30. package/schema/DatabaseSchema.d.ts +5 -2
  31. package/schema/DatabaseSchema.js +26 -8
  32. package/schema/DatabaseTable.d.ts +4 -2
  33. package/schema/DatabaseTable.js +54 -29
  34. package/schema/SchemaComparator.js +12 -2
  35. package/schema/SchemaHelper.d.ts +6 -4
  36. package/schema/SchemaHelper.js +11 -4
  37. package/schema/SqlSchemaGenerator.d.ts +3 -6
  38. package/schema/SqlSchemaGenerator.js +61 -28
  39. package/typings.d.ts +13 -7
@@ -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);
291
+ options.processCollections ??= true;
292
+ options.convertCustomTypes ??= true;
240
293
  const meta = this.metadata.find(entityName);
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,43 @@ 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 = [];
261
325
  props.forEach(prop => {
262
326
  if (prop.fieldNames.length > 1) {
263
- params.push(...(row[prop.name] ?? prop.fieldNames.map(() => null)));
264
- keys.push(...prop.fieldNames.map(_ => '?'));
327
+ const param = core_1.Utils.flatten([...row[prop.name] ?? prop.fieldNames.map(() => null)]);
328
+ const key = param.map(() => '?');
329
+ prop.fieldNames.forEach((field, idx) => {
330
+ if (!duplicates.includes(field)) {
331
+ params.push(param[idx]);
332
+ keys.push(key[idx]);
333
+ }
334
+ });
265
335
  }
266
336
  else if (prop.customType && 'convertToDatabaseValueSQL' in prop.customType && !this.platform.isRaw(row[prop.name])) {
267
337
  keys.push(prop.customType.convertToDatabaseValueSQL('?', this.platform));
268
- params.push(row[prop.name]);
338
+ addParams(prop, row);
269
339
  }
270
340
  else {
271
- params.push(row[prop.name]);
341
+ addParams(prop, row);
272
342
  keys.push('?');
273
343
  }
274
344
  });
275
345
  return '(' + (keys.join(', ') || 'default') + ')';
276
346
  }).join(', ');
277
347
  }
278
- if (this.platform.usesReturningStatement()) {
279
- /* istanbul ignore next */
280
- const returningProps = meta.hydrateProps.filter(prop => prop.persist !== false && (prop.primary || prop.defaultRaw));
348
+ if (meta && this.platform.usesReturningStatement()) {
349
+ const returningProps = meta.props
350
+ .filter(prop => prop.persist !== false && ((prop.primary && prop.autoincrement) || prop.defaultRaw))
351
+ .filter(prop => !(prop.name in data[0]) || core_1.Utils.isRawSql(data[0][prop.name]));
281
352
  const returningFields = core_1.Utils.flatten(returningProps.map(prop => prop.fieldNames));
282
353
  /* istanbul ignore next */
283
354
  sql += returningFields.length > 0 ? ` returning ${returningFields.map(field => this.platform.quoteIdentifier(field)).join(', ')}` : '';
@@ -289,8 +360,8 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
289
360
  pk = data.map(d => core_1.Utils.getPrimaryKeyCond(d, pks));
290
361
  }
291
362
  else {
292
- res.row ?? (res.row = {});
293
- res.rows ?? (res.rows = []);
363
+ res.row ??= {};
364
+ res.rows ??= [];
294
365
  pk = data.map((d, i) => d[pks[0]] ?? res.rows[i]?.[pks[0]]).map(d => [d]);
295
366
  res.insertId = res.insertId || res.row[pks[0]];
296
367
  }
@@ -300,7 +371,7 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
300
371
  return res;
301
372
  }
302
373
  async nativeUpdate(entityName, where, data, options = {}) {
303
- options.convertCustomTypes ?? (options.convertCustomTypes = true);
374
+ options.convertCustomTypes ??= true;
304
375
  const meta = this.metadata.find(entityName);
305
376
  const pks = this.getPrimaryKeyFields(entityName);
306
377
  const collections = this.extractManyToMany(entityName, data);
@@ -314,11 +385,18 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
314
385
  .withSchema(this.getSchemaName(meta, options));
315
386
  if (options.upsert) {
316
387
  /* istanbul ignore next */
317
- const uniqueFields = core_1.Utils.isPlainObject(where) ? Object.keys(where) : meta.primaryKeys;
318
- /* istanbul ignore next */
388
+ const uniqueFields = options.onConflictFields ?? (core_1.Utils.isPlainObject(where) ? core_1.Utils.keys(where) : meta.primaryKeys);
389
+ const returning = (0, core_1.getOnConflictReturningFields)(meta, data, uniqueFields, options);
319
390
  qb.insert(data)
320
- .onConflict(uniqueFields.map(p => meta?.properties[p]?.fieldNames[0] ?? p))
321
- .merge(Object.keys(data).filter(f => !uniqueFields.includes(f)));
391
+ .onConflict(uniqueFields)
392
+ .returning(returning);
393
+ if (!options.onConflictAction || options.onConflictAction === 'merge') {
394
+ const fields = (0, core_1.getOnConflictFields)(data, uniqueFields, options);
395
+ qb.merge(fields);
396
+ }
397
+ if (options.onConflictAction === 'ignore') {
398
+ qb.ignore();
399
+ }
322
400
  }
323
401
  else {
324
402
  qb.update(data).where(where);
@@ -331,20 +409,36 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
331
409
  return res;
332
410
  }
333
411
  async nativeUpdateMany(entityName, where, data, options = {}) {
334
- options.processCollections ?? (options.processCollections = true);
335
- options.convertCustomTypes ?? (options.convertCustomTypes = true);
412
+ options.processCollections ??= true;
413
+ options.convertCustomTypes ??= true;
336
414
  const meta = this.metadata.get(entityName);
337
415
  if (options.upsert) {
338
- const uniqueFields = core_1.Utils.isPlainObject(where[0]) ? Object.keys(where[0]) : meta.primaryKeys;
416
+ const uniqueFields = options.onConflictFields ?? (core_1.Utils.isPlainObject(where[0]) ? Object.keys(where[0]) : meta.primaryKeys);
339
417
  const qb = this.createQueryBuilder(entityName, options.ctx, 'write', options.convertCustomTypes).withSchema(this.getSchemaName(meta, options));
418
+ const returning = (0, core_1.getOnConflictReturningFields)(meta, data[0], uniqueFields, options);
340
419
  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)));
420
+ .onConflict(uniqueFields)
421
+ .returning(returning);
422
+ if (!options.onConflictAction || options.onConflictAction === 'merge') {
423
+ const fields = (0, core_1.getOnConflictFields)(data[0], uniqueFields, options);
424
+ qb.merge(fields);
425
+ }
426
+ if (options.onConflictAction === 'ignore') {
427
+ qb.ignore();
428
+ }
343
429
  return qb.execute('run', false);
344
430
  }
345
431
  const collections = options.processCollections ? data.map(d => this.extractManyToMany(entityName, d)) : [];
346
432
  const keys = new Set();
347
- data.forEach(row => Object.keys(row).forEach(k => keys.add(k)));
433
+ const returning = new Set();
434
+ data.forEach(row => {
435
+ core_1.Utils.keys(row).forEach(k => {
436
+ keys.add(k);
437
+ if (core_1.Utils.isRawSql(row[k])) {
438
+ returning.add(k);
439
+ }
440
+ });
441
+ });
348
442
  const pkCond = core_1.Utils.flatten(meta.primaryKeys.map(pk => meta.properties[pk].fieldNames)).map(pk => `${this.platform.quoteIdentifier(pk)} = ?`).join(' and ');
349
443
  const params = [];
350
444
  let sql = `update ${this.getTableName(meta, options)} set `;
@@ -373,7 +467,7 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
373
467
  const versionProperty = meta.properties[meta.versionProperty];
374
468
  const quotedFieldName = this.platform.quoteIdentifier(versionProperty.fieldNames[0]);
375
469
  sql += `${quotedFieldName} = `;
376
- if (versionProperty.type.toLowerCase() === 'date') {
470
+ if (versionProperty.runtimeType === 'Date') {
377
471
  sql += this.platform.getCurrentTimestampSQL(versionProperty.length);
378
472
  }
379
473
  else {
@@ -404,6 +498,11 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
404
498
  return '?';
405
499
  });
406
500
  sql += ` in (${conds.join(', ')})`;
501
+ if (this.platform.usesReturningStatement() && returning.size > 0) {
502
+ const returningFields = core_1.Utils.flatten([...returning].map(prop => meta.properties[prop].fieldNames));
503
+ /* istanbul ignore next */
504
+ sql += returningFields.length > 0 ? ` returning ${returningFields.map(field => this.platform.quoteIdentifier(field)).join(', ')}` : '';
505
+ }
407
506
  const res = await this.rethrow(this.execute(sql, params, 'run', options.ctx));
408
507
  for (let i = 0; i < collections.length; i++) {
409
508
  await this.processManyToMany(meta, where[i], collections[i], false, options);
@@ -438,30 +537,53 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
438
537
  deleteDiff.push(...snapshot);
439
538
  insertDiff.push(...current);
440
539
  }
441
- if (coll.property.reference === core_1.ReferenceType.ONE_TO_MANY) {
540
+ if (coll.property.kind === core_1.ReferenceKind.ONE_TO_MANY) {
442
541
  const cols = coll.property.referencedColumnNames;
443
542
  const qb = this.createQueryBuilder(coll.property.type, ctx, 'write')
444
- .withSchema(this.getSchemaName(meta, options))
445
- .update({ [coll.property.mappedBy]: pks })
543
+ .withSchema(this.getSchemaName(meta, options));
544
+ if (coll.getSnapshot() === undefined) {
545
+ if (coll.property.orphanRemoval) {
546
+ const kqb = qb.delete({ [coll.property.mappedBy]: pks })
547
+ .getKnexQuery()
548
+ .whereNotIn(cols, insertDiff);
549
+ return this.rethrow(this.execute(kqb));
550
+ }
551
+ const kqb = qb.update({ [coll.property.mappedBy]: null })
552
+ .getKnexQuery()
553
+ .whereNotIn(cols, insertDiff);
554
+ return this.rethrow(this.execute(kqb));
555
+ }
556
+ const kqb = qb.update({ [coll.property.mappedBy]: pks })
446
557
  .getKnexQuery()
447
558
  .whereIn(cols, insertDiff);
448
- return this.rethrow(this.execute(qb));
559
+ return this.rethrow(this.execute(kqb));
449
560
  }
450
561
  /* istanbul ignore next */
451
562
  const ownerSchema = wrapped.getSchema() === '*' ? this.config.get('schema') : wrapped.getSchema();
452
563
  const pivotMeta = this.metadata.find(coll.property.pivotEntity);
453
564
  if (pivotMeta.schema === '*') {
454
565
  /* istanbul ignore next */
455
- options ?? (options = {});
566
+ options ??= {};
456
567
  options.schema = ownerSchema;
457
568
  }
458
569
  return this.rethrow(this.updateCollectionDiff(meta, coll.property, pks, deleteDiff, insertDiff, options));
459
570
  }
460
571
  async loadFromPivotTable(prop, owners, where = {}, orderBy, ctx, options) {
461
572
  const pivotProp2 = this.getPivotInverseProperty(prop);
573
+ const prop2 = prop.targetMeta.properties[prop.mappedBy ?? prop.inversedBy];
462
574
  const ownerMeta = this.metadata.find(pivotProp2.type);
463
575
  const pivotMeta = this.metadata.find(prop.pivotEntity);
464
- const cond = { [`${prop.pivotEntity}.${pivotProp2.name}`]: { $in: ownerMeta.compositePK ? owners : owners.map(o => o[0]) } };
576
+ options = { ...options };
577
+ const qb = this.createQueryBuilder(prop.type, ctx, options.connectionType)
578
+ .withSchema(this.getSchemaName(prop.targetMeta, options))
579
+ .indexHint(options.indexHint)
580
+ .comment(options.comments)
581
+ .hintComment(options.hintComments);
582
+ const pivotAlias = qb.getNextAlias(pivotMeta.tableName);
583
+ const pivotKey = pivotProp2.joinColumns.map(column => `${pivotAlias}.${column}`).join(core_1.Utils.PK_SEPARATOR);
584
+ const cond = {
585
+ [pivotKey]: { $in: ownerMeta.compositePK ? owners : owners.map(o => o[0]) },
586
+ };
465
587
  /* istanbul ignore if */
466
588
  if (!core_1.Utils.isEmpty(where) && Object.keys(where).every(k => core_1.Utils.isOperator(k, false))) {
467
589
  where = cond;
@@ -469,22 +591,32 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
469
591
  else {
470
592
  where = { ...where, ...cond };
471
593
  }
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)) {
594
+ orderBy = this.getPivotOrderBy(prop, pivotAlias, orderBy);
595
+ const populate = this.autoJoinOneToOneOwner(prop.targetMeta, []);
596
+ const fields = this.buildFields(prop.targetMeta, (options.populate ?? []), [], qb, options.fields);
597
+ const k1 = !prop.owner ? 'joinColumns' : 'inverseJoinColumns';
598
+ const k2 = prop.owner ? 'joinColumns' : 'inverseJoinColumns';
599
+ const cols = [
600
+ ...prop[k1].map(col => `${pivotAlias}.${col} as fk__${col}`),
601
+ ...prop[k2].map(col => `${pivotAlias}.${col} as fk__${col}`),
602
+ ];
603
+ fields.push(...cols);
604
+ qb.select(fields)
605
+ .join(prop2.name, pivotAlias, {}, 'pivotJoin', undefined, options.schema)
606
+ .populate(populate)
607
+ .where(where)
608
+ .orderBy(orderBy)
609
+ .setLockMode(options.lockMode, options.lockTableAliases);
610
+ if (owners.length === 1 && (options.offset != null || options.limit != null)) {
480
611
  qb.limit(options.limit, options.offset);
481
612
  }
482
- if (prop.targetMeta.schema !== '*' && pivotMeta.schema === '*' && options?.schema) {
613
+ const schema = this.config.get('schema');
614
+ if (prop.targetMeta.schema !== '*' && pivotMeta.schema === '*' && options.schema) {
483
615
  // eslint-disable-next-line dot-notation
484
616
  qb['finalize']();
485
617
  // eslint-disable-next-line dot-notation
486
618
  Object.values(qb['_joins']).forEach(join => {
487
- join.schema = options.schema;
619
+ join.schema = schema;
488
620
  });
489
621
  }
490
622
  const items = owners.length ? await this.rethrow(qb.execute('all')) : [];
@@ -508,6 +640,18 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
508
640
  });
509
641
  return map;
510
642
  }
643
+ getPivotOrderBy(prop, pivotAlias, orderBy) {
644
+ if (!core_1.Utils.isEmpty(orderBy)) {
645
+ return orderBy;
646
+ }
647
+ if (!core_1.Utils.isEmpty(prop.orderBy)) {
648
+ return core_1.Utils.asArray(prop.orderBy);
649
+ }
650
+ if (prop.fixedOrder) {
651
+ return [{ [`${pivotAlias}.${prop.fixedOrderColumn}`]: core_1.QueryOrder.ASC }];
652
+ }
653
+ return [];
654
+ }
511
655
  async execute(queryOrKnex, params = [], method = 'all', ctx) {
512
656
  return this.rethrow(this.connection.execute(queryOrKnex, params, method, ctx));
513
657
  }
@@ -520,7 +664,7 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
520
664
  }
521
665
  const relationsToPopulate = populate.map(({ field }) => field);
522
666
  const toPopulate = meta.relations
523
- .filter(prop => prop.reference === core_1.ReferenceType.ONE_TO_ONE && !prop.owner && !relationsToPopulate.includes(prop.name))
667
+ .filter(prop => prop.kind === core_1.ReferenceKind.ONE_TO_ONE && !prop.owner && !relationsToPopulate.includes(prop.name))
524
668
  .filter(prop => fields.length === 0 || fields.some(f => prop.name === f || prop.name.startsWith(`${String(f)}.`)))
525
669
  .map(prop => ({ field: prop.name, strategy: prop.strategy }));
526
670
  return [...populate, ...toPopulate];
@@ -528,23 +672,30 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
528
672
  joinedProps(meta, populate) {
529
673
  return populate.filter(p => {
530
674
  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;
675
+ return (p.strategy || prop.strategy || this.config.get('loadStrategy')) === core_1.LoadStrategy.JOINED && prop.kind !== core_1.ReferenceKind.SCALAR;
532
676
  });
533
677
  }
534
678
  /**
535
679
  * @internal
536
680
  */
537
- mergeJoinedResult(rawResults, meta) {
681
+ mergeJoinedResult(rawResults, meta, joinedProps) {
538
682
  // group by the root entity primary key first
539
683
  const map = {};
540
684
  const res = [];
541
685
  rawResults.forEach(item => {
542
686
  const pk = core_1.Utils.getCompositeKeyHash(item, meta);
543
687
  if (map[pk]) {
544
- map[pk].push(item);
688
+ joinedProps.forEach(hint => {
689
+ // Sometimes we might join a M:N relation with additional filter on the target entity, and as a result, we get
690
+ // the first result with `null` for all target values, which is mapped as empty array. When we see that happen,
691
+ // we need to merge the results of the next item.
692
+ if (Array.isArray(map[pk][hint.field]) && Array.isArray(item[hint.field]) && map[pk][hint.field].length === 0) {
693
+ item[hint.field].forEach((el) => map[pk][hint.field].push(el));
694
+ }
695
+ });
545
696
  }
546
697
  else {
547
- map[pk] = [item];
698
+ map[pk] = item;
548
699
  res.push(item);
549
700
  }
550
701
  });
@@ -553,14 +704,17 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
553
704
  getFieldsForJoinedLoad(qb, meta, explicitFields, populate = [], parentTableAlias, parentJoinPath) {
554
705
  const fields = [];
555
706
  const joinedProps = this.joinedProps(meta, populate);
707
+ if (explicitFields?.includes('*')) {
708
+ fields.push('*');
709
+ }
556
710
  const shouldHaveColumn = (prop, populate, fields) => {
557
711
  if (!this.platform.shouldHaveColumn(prop, populate)) {
558
712
  return false;
559
713
  }
560
714
  if (!fields || prop.primary) {
561
- return true;
715
+ return !fields?.includes('*');
562
716
  }
563
- return fields.includes(prop.name);
717
+ return fields.some(f => f === prop.name || f.toString().startsWith(prop.name + '.'));
564
718
  };
565
719
  // alias all fields in the primary table
566
720
  meta.props
@@ -587,24 +741,25 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
587
741
  * @internal
588
742
  */
589
743
  mapPropToFieldNames(qb, prop, tableAlias) {
590
- const aliased = qb.ref(tableAlias ? `${tableAlias}__${prop.fieldNames[0]}` : prop.fieldNames[0]).toString();
744
+ const knex = this.connection.getKnex();
745
+ const aliased = knex.ref(tableAlias ? `${tableAlias}__${prop.fieldNames[0]}` : prop.fieldNames[0]).toString();
591
746
  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()];
747
+ const prefixed = knex.ref(prop.fieldNames[0]).withSchema(tableAlias).toString();
748
+ return [(0, core_1.raw)(`${prop.customType.convertToJSValueSQL(prefixed, this.platform)} as ${aliased}`)];
594
749
  }
595
750
  if (prop.formula) {
596
- const alias = qb.ref(tableAlias ?? qb.alias).toString();
597
- return [`${prop.formula(alias)} as ${aliased}`];
751
+ const alias = knex.ref(tableAlias ?? qb.alias).toString();
752
+ return [(0, core_1.raw)(`${prop.formula(alias)} as ${aliased}`)];
598
753
  }
599
754
  if (tableAlias) {
600
- return prop.fieldNames.map(fieldName => qb.ref(fieldName).withSchema(tableAlias).as(`${tableAlias}__${fieldName}`));
755
+ return prop.fieldNames.map(fieldName => knex.ref(fieldName).withSchema(tableAlias).as(`${tableAlias}__${fieldName}`));
601
756
  }
602
757
  return prop.fieldNames;
603
758
  }
604
759
  /** @internal */
605
- createQueryBuilder(entityName, ctx, preferredConnectionType, convertCustomTypes) {
760
+ createQueryBuilder(entityName, ctx, preferredConnectionType, convertCustomTypes, logging) {
606
761
  const connectionType = this.resolveConnectionType({ ctx, connectionType: preferredConnectionType });
607
- const qb = new query_1.QueryBuilder(entityName, this.metadata, this, ctx, undefined, connectionType);
762
+ const qb = new query_1.QueryBuilder(entityName, this.metadata, this, ctx, undefined, connectionType, undefined, logging);
608
763
  if (!convertCustomTypes) {
609
764
  qb.unsetFlag(core_1.QueryFlag.CONVERT_CUSTOM_TYPES);
610
765
  }
@@ -628,7 +783,7 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
628
783
  }
629
784
  const ret = {};
630
785
  this.metadata.find(entityName).relations.forEach(prop => {
631
- if (prop.reference === core_1.ReferenceType.MANY_TO_MANY && data[prop.name]) {
786
+ if (prop.kind === core_1.ReferenceKind.MANY_TO_MANY && data[prop.name]) {
632
787
  ret[prop.name] = data[prop.name].map((item) => core_1.Utils.asArray(item));
633
788
  delete data[prop.name];
634
789
  }
@@ -649,10 +804,9 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
649
804
  if (!deleteDiff) {
650
805
  deleteDiff = [];
651
806
  }
807
+ const pivotMeta = this.metadata.find(prop.pivotEntity);
652
808
  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);
809
+ const qb1 = this.createQueryBuilder(prop.pivotEntity, options?.ctx, 'write').withSchema(this.getSchemaName(pivotMeta, options));
656
810
  const knex = qb1.getKnex();
657
811
  if (Array.isArray(deleteDiff)) {
658
812
  knex.whereIn(prop.inverseJoinColumns, deleteDiff);
@@ -671,13 +825,16 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
671
825
  });
672
826
  /* istanbul ignore else */
673
827
  if (this.platform.allowsMultiInsert()) {
674
- await this.nativeInsertMany(prop.pivotEntity, items, { ...options, convertCustomTypes: false, processCollections: false });
828
+ await this.nativeInsertMany(prop.pivotEntity, items, {
829
+ ...options,
830
+ convertCustomTypes: false,
831
+ processCollections: false,
832
+ });
675
833
  }
676
834
  else {
677
835
  await core_1.Utils.runSerial(items, item => {
678
836
  return this.createQueryBuilder(prop.pivotEntity, options?.ctx, 'write')
679
- .unsetFlag(core_1.QueryFlag.CONVERT_CUSTOM_TYPES)
680
- .withSchema(this.getSchemaName(meta, options))
837
+ .withSchema(this.getSchemaName(pivotMeta, options))
681
838
  .insert(item)
682
839
  .execute('run', false);
683
840
  });
@@ -685,9 +842,9 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
685
842
  }
686
843
  async lockPessimistic(entity, options) {
687
844
  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);
845
+ const qb = this.createQueryBuilder(entity.constructor.name, options.ctx).withSchema(options.schema ?? meta.schema);
689
846
  const cond = core_1.Utils.getPrimaryKeyCond(entity, meta.primaryKeys);
690
- qb.select('1').where(cond).setLockMode(options.lockMode, options.lockTableAliases);
847
+ qb.select((0, core_1.raw)('1')).where(cond).setLockMode(options.lockMode, options.lockTableAliases);
691
848
  await this.rethrow(qb.execute());
692
849
  }
693
850
  buildJoinedPropsOrderBy(entityName, qb, meta, populate, parentPath) {
@@ -699,7 +856,7 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
699
856
  const path = `${parentPath ? parentPath : entityName}.${relation.field}`;
700
857
  const propAlias = qb.getAliasForJoinPath(path);
701
858
  if (propOrderBy) {
702
- Object.keys(propOrderBy).forEach(field => {
859
+ core_1.Utils.keys(propOrderBy).forEach(field => {
703
860
  orderBy.push({ [`${propAlias}.${field}`]: propOrderBy[field] });
704
861
  });
705
862
  }
@@ -726,12 +883,12 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
726
883
  return ret;
727
884
  }
728
885
  processField(meta, prop, field, ret, populate, joinedProps, qb) {
729
- if (!prop || (prop.reference === core_1.ReferenceType.ONE_TO_ONE && !prop.owner)) {
886
+ if (!prop || (prop.kind === core_1.ReferenceKind.ONE_TO_ONE && !prop.owner)) {
730
887
  return;
731
888
  }
732
- if (prop.reference === core_1.ReferenceType.EMBEDDED) {
889
+ if (prop.kind === core_1.ReferenceKind.EMBEDDED) {
733
890
  if (prop.object) {
734
- ret.push(prop.fieldNames[0]);
891
+ ret.push(prop.name);
735
892
  return;
736
893
  }
737
894
  const parts = field.split('.');
@@ -743,9 +900,7 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
743
900
  }
744
901
  return;
745
902
  }
746
- if (prop.fieldNames) {
747
- ret.push(prop.fieldNames[0]);
748
- }
903
+ ret.push(prop.name);
749
904
  }
750
905
  buildFields(meta, populate, joinedProps, qb, fields) {
751
906
  const lazyProps = meta.props.filter(prop => prop.lazy && !populate.some(p => p.field === prop.name || p.all));
@@ -753,6 +908,7 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
753
908
  const requiresSQLConversion = meta.props.some(p => p.customType?.convertToJSValueSQL);
754
909
  const hasExplicitFields = !!fields;
755
910
  const ret = [];
911
+ let addFormulas = false;
756
912
  if (joinedProps.length > 0) {
757
913
  ret.push(...this.getFieldsForJoinedLoad(qb, meta, fields, populate));
758
914
  }
@@ -778,20 +934,22 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
778
934
  else if (lazyProps.filter(p => !p.formula).length > 0) {
779
935
  const props = meta.props.filter(prop => this.platform.shouldHaveColumn(prop, populate, false));
780
936
  ret.push(...core_1.Utils.flatten(props.filter(p => !lazyProps.includes(p)).map(p => p.fieldNames)));
937
+ addFormulas = true;
781
938
  }
782
939
  else if (hasLazyFormulas || requiresSQLConversion) {
783
940
  ret.push('*');
941
+ addFormulas = true;
784
942
  }
785
- if (ret.length > 0 && !hasExplicitFields) {
943
+ if (ret.length > 0 && !hasExplicitFields && addFormulas) {
786
944
  meta.props
787
945
  .filter(prop => prop.formula && !lazyProps.includes(prop))
788
946
  .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}`);
947
+ const alias = this.connection.getKnex().ref(qb.alias).toString();
948
+ const aliased = this.connection.getKnex().ref(prop.fieldNames[0]).toString();
949
+ ret.push((0, core_1.raw)(`${prop.formula(alias)} as ${aliased}`));
792
950
  });
793
951
  meta.props
794
- .filter(prop => prop.customType?.convertToDatabaseValueSQL || prop.customType?.convertToJSValueSQL)
952
+ .filter(prop => prop.hasConvertToDatabaseValueSQL || prop.hasConvertToJSValueSQL)
795
953
  .forEach(prop => ret.push(prop.name));
796
954
  }
797
955
  return ret.length > 0 ? core_1.Utils.unique(ret) : ['*'];