@mikro-orm/knex 6.0.0-dev.18 → 6.0.0-dev.180

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 (44) hide show
  1. package/AbstractSqlConnection.d.ts +8 -4
  2. package/AbstractSqlConnection.js +30 -18
  3. package/AbstractSqlDriver.d.ts +21 -23
  4. package/AbstractSqlDriver.js +389 -224
  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/PivotCollectionPersister.d.ts +17 -0
  10. package/PivotCollectionPersister.js +128 -0
  11. package/README.md +64 -107
  12. package/SqlEntityManager.d.ts +2 -6
  13. package/SqlEntityManager.js +5 -9
  14. package/SqlEntityRepository.d.ts +7 -6
  15. package/SqlEntityRepository.js +11 -8
  16. package/index.mjs +24 -5
  17. package/package.json +8 -8
  18. package/query/ArrayCriteriaNode.d.ts +4 -3
  19. package/query/ArrayCriteriaNode.js +5 -0
  20. package/query/CriteriaNode.d.ts +10 -8
  21. package/query/CriteriaNode.js +23 -10
  22. package/query/CriteriaNodeFactory.d.ts +6 -6
  23. package/query/CriteriaNodeFactory.js +28 -14
  24. package/query/ObjectCriteriaNode.d.ts +4 -3
  25. package/query/ObjectCriteriaNode.js +47 -17
  26. package/query/QueryBuilder.d.ts +53 -24
  27. package/query/QueryBuilder.js +393 -133
  28. package/query/QueryBuilderHelper.d.ts +11 -16
  29. package/query/QueryBuilderHelper.js +194 -155
  30. package/query/ScalarCriteriaNode.d.ts +3 -2
  31. package/query/ScalarCriteriaNode.js +10 -6
  32. package/query/enums.d.ts +7 -0
  33. package/query/enums.js +10 -2
  34. package/schema/DatabaseSchema.d.ts +5 -2
  35. package/schema/DatabaseSchema.js +28 -10
  36. package/schema/DatabaseTable.d.ts +13 -5
  37. package/schema/DatabaseTable.js +384 -60
  38. package/schema/SchemaComparator.d.ts +5 -5
  39. package/schema/SchemaComparator.js +59 -35
  40. package/schema/SchemaHelper.d.ts +6 -4
  41. package/schema/SchemaHelper.js +19 -8
  42. package/schema/SqlSchemaGenerator.d.ts +3 -6
  43. package/schema/SqlSchemaGenerator.js +71 -36
  44. package/typings.d.ts +19 -10
@@ -4,10 +4,14 @@ exports.AbstractSqlDriver = void 0;
4
4
  const core_1 = require("@mikro-orm/core");
5
5
  const query_1 = require("./query");
6
6
  const SqlEntityManager_1 = require("./SqlEntityManager");
7
+ const PivotCollectionPersister_1 = require("./PivotCollectionPersister");
7
8
  class AbstractSqlDriver extends core_1.DatabaseDriver {
9
+ [core_1.EntityManagerType];
10
+ connection;
11
+ replicas = [];
12
+ platform;
8
13
  constructor(config, platform, connection, connector) {
9
14
  super(config, connector);
10
- this.replicas = [];
11
15
  this.connection = new connection(this.config);
12
16
  this.replicas = this.createReplicas(conf => new connection(this.config, conf, 'read'));
13
17
  this.platform = platform;
@@ -26,19 +30,31 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
26
30
  }
27
31
  const populate = this.autoJoinOneToOneOwner(meta, options.populate, options.fields);
28
32
  const joinedProps = this.joinedProps(meta, populate);
29
- const qb = this.createQueryBuilder(entityName, options.ctx, options.connectionType, false);
30
- const fields = this.buildFields(meta, populate, joinedProps, qb, options.fields);
33
+ const qb = this.createQueryBuilder(entityName, options.ctx, options.connectionType, false, options.logging);
34
+ const fields = this.buildFields(meta, populate, joinedProps, qb, qb.alias, options.fields);
31
35
  const joinedPropsOrderBy = this.buildJoinedPropsOrderBy(entityName, qb, meta, joinedProps);
36
+ const orderBy = [...core_1.Utils.asArray(options.orderBy), ...joinedPropsOrderBy];
32
37
  if (core_1.Utils.isPrimaryKey(where, meta.compositePK)) {
33
38
  where = { [core_1.Utils.getPrimaryKeyHash(meta.primaryKeys)]: where };
34
39
  }
40
+ const { first, last, before, after } = options;
41
+ const isCursorPagination = [first, last, before, after].some(v => v != null);
35
42
  qb.select(fields)
36
43
  .populate(populate, joinedProps.length > 0 ? options.populateWhere : undefined)
37
44
  .where(where)
38
- .orderBy([...core_1.Utils.asArray(options.orderBy), ...joinedPropsOrderBy])
39
45
  .groupBy(options.groupBy)
40
46
  .having(options.having)
47
+ .indexHint(options.indexHint)
48
+ .comment(options.comments)
49
+ .hintComment(options.hintComments)
41
50
  .withSchema(this.getSchemaName(meta, options));
51
+ if (isCursorPagination) {
52
+ const { orderBy: newOrderBy, where } = this.processCursorOptions(meta, options, orderBy);
53
+ qb.andWhere(where).orderBy(newOrderBy);
54
+ }
55
+ else {
56
+ qb.orderBy(orderBy);
57
+ }
42
58
  if (options.limit !== undefined) {
43
59
  qb.limit(options.limit, options.offset);
44
60
  }
@@ -47,8 +63,8 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
47
63
  }
48
64
  core_1.Utils.asArray(options.flags).forEach(flag => qb.setFlag(flag));
49
65
  const result = await this.rethrow(qb.execute('all'));
50
- if (joinedProps.length > 0) {
51
- return this.mergeJoinedResult(result, meta);
66
+ if (isCursorPagination && !first && !!last) {
67
+ result.reverse();
52
68
  }
53
69
  return result;
54
70
  }
@@ -61,58 +77,59 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
61
77
  opts.limit = 1;
62
78
  }
63
79
  if (opts.limit > 0 && !opts.flags?.includes(core_1.QueryFlag.DISABLE_PAGINATE)) {
64
- opts.flags ?? (opts.flags = []);
80
+ opts.flags ??= [];
65
81
  opts.flags.push(core_1.QueryFlag.DISABLE_PAGINATE);
66
82
  }
67
83
  const res = await this.find(entityName, where, opts);
68
84
  return res[0] || null;
69
85
  }
70
86
  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;
87
+ return this.findFromVirtual(entityName, where, options, query_1.QueryType.SELECT);
87
88
  }
88
89
  async countVirtual(entityName, where, options) {
90
+ return this.findFromVirtual(entityName, where, options, query_1.QueryType.COUNT);
91
+ }
92
+ async findFromVirtual(entityName, where, options, type) {
89
93
  const meta = this.metadata.get(entityName);
90
94
  /* istanbul ignore next */
91
95
  if (!meta.expression) {
92
- return 0;
96
+ return type === query_1.QueryType.SELECT ? [] : 0;
93
97
  }
94
98
  if (typeof meta.expression === 'string') {
95
- return this.wrapVirtualExpressionInSubquery(meta, meta.expression, where, options, query_1.QueryType.COUNT);
99
+ return this.wrapVirtualExpressionInSubquery(meta, meta.expression, where, options, type);
96
100
  }
97
101
  const em = this.createEntityManager(false);
98
102
  em.setTransactionContext(options.ctx);
99
103
  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);
104
+ if (typeof res === 'string') {
105
+ return this.wrapVirtualExpressionInSubquery(meta, res, where, options, type);
106
+ }
107
+ if (res instanceof query_1.QueryBuilder) {
108
+ return this.wrapVirtualExpressionInSubquery(meta, res.getFormattedQuery(), where, options, type);
109
+ }
110
+ if (core_1.Utils.isObject(res)) {
111
+ const { sql, bindings } = res.toSQL();
112
+ const query = this.platform.formatQuery(sql, bindings);
113
+ return this.wrapVirtualExpressionInSubquery(meta, query, where, options, type);
102
114
  }
103
115
  /* istanbul ignore next */
104
116
  return res;
105
117
  }
106
- async wrapVirtualExpressionInSubquery(meta, expression, where, options, type = query_1.QueryType.SELECT) {
118
+ async wrapVirtualExpressionInSubquery(meta, expression, where, options, type) {
107
119
  const qb = this.createQueryBuilder(meta.className, options?.ctx, options.connectionType, options.convertCustomTypes)
108
- .limit(options?.limit, options?.offset);
109
- if (options.orderBy) {
110
- qb.orderBy(options.orderBy);
120
+ .indexHint(options.indexHint)
121
+ .comment(options.comments)
122
+ .hintComment(options.hintComments);
123
+ if (type !== query_1.QueryType.COUNT) {
124
+ qb.limit(options?.limit, options?.offset);
125
+ if (options.orderBy) {
126
+ qb.orderBy(options.orderBy);
127
+ }
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,6 +148,7 @@ 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);
135
153
  }
136
154
  return ret;
@@ -153,12 +171,25 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
153
171
  return root[`${relationAlias}__${name}`] != null;
154
172
  }));
155
173
  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] || [];
174
+ if ([core_1.ReferenceKind.MANY_TO_MANY, core_1.ReferenceKind.ONE_TO_MANY].includes(relation.kind)) {
175
+ result[relation.name] ??= [];
176
+ }
177
+ if ([core_1.ReferenceKind.MANY_TO_ONE, core_1.ReferenceKind.ONE_TO_ONE].includes(relation.kind)) {
178
+ result[relation.name] = null;
159
179
  }
160
180
  return;
161
181
  }
182
+ meta2.props
183
+ .filter(prop => prop.persist === false && prop.fieldNames)
184
+ .forEach(prop => {
185
+ if (prop.fieldNames.length > 1) { // composite keys
186
+ relationPojo[prop.name] = prop.fieldNames.map(name => root[`${relationAlias}__${name}`]);
187
+ }
188
+ else {
189
+ const alias = `${relationAlias}__${prop.fieldNames[0]}`;
190
+ relationPojo[prop.name] = root[alias];
191
+ }
192
+ });
162
193
  meta2.props
163
194
  .filter(prop => this.platform.shouldHaveColumn(prop, p.children || []))
164
195
  .forEach(prop => {
@@ -166,22 +197,20 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
166
197
  relationPojo[prop.name] = prop.fieldNames.map(name => root[`${relationAlias}__${name}`]);
167
198
  prop.fieldNames.map(name => delete root[`${relationAlias}__${name}`]);
168
199
  }
200
+ else if (prop.runtimeType === 'Date') {
201
+ const alias = `${relationAlias}__${prop.fieldNames[0]}`;
202
+ relationPojo[prop.name] = (typeof root[alias] === 'string' ? new Date(root[alias]) : root[alias]);
203
+ delete root[alias];
204
+ }
169
205
  else {
170
206
  const alias = `${relationAlias}__${prop.fieldNames[0]}`;
171
207
  relationPojo[prop.name] = root[alias];
172
208
  delete root[alias];
173
209
  }
174
210
  });
175
- const key = `${meta.name}-${(core_1.Utils.getCompositeKeyHash(result, meta))}`;
176
- if (map[key]) {
177
- result[relation.name] = map[key][relation.name];
178
- }
179
- else {
180
- map[key] = result;
181
- }
182
- if ([core_1.ReferenceType.MANY_TO_MANY, core_1.ReferenceType.ONE_TO_MANY].includes(relation.reference)) {
183
- result[relation.name] = result[relation.name] || [];
184
- this.appendToCollection(meta2, result[relation.name], relationPojo);
211
+ if ([core_1.ReferenceKind.MANY_TO_MANY, core_1.ReferenceKind.ONE_TO_MANY].includes(relation.kind)) {
212
+ result[relation.name] ??= [];
213
+ result[relation.name].push(relationPojo);
185
214
  }
186
215
  else {
187
216
  result[relation.name] = relationPojo;
@@ -190,23 +219,15 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
190
219
  this.mapJoinedProps(relationPojo, meta2, populateChildren, qb, root, map, path);
191
220
  });
192
221
  }
193
- appendToCollection(meta, collection, relationPojo) {
194
- if (collection.length === 0) {
195
- return void collection.push(relationPojo);
196
- }
197
- const last = collection[collection.length - 1];
198
- const pk1 = core_1.Utils.getCompositeKeyHash(last, meta);
199
- const pk2 = core_1.Utils.getCompositeKeyHash(relationPojo, meta);
200
- if (pk1 !== pk2) {
201
- collection.push(relationPojo);
202
- }
203
- }
204
222
  async count(entityName, where, options = {}) {
205
223
  const meta = this.metadata.find(entityName);
206
224
  if (meta?.virtual) {
207
225
  return this.countVirtual(entityName, where, options);
208
226
  }
209
227
  const qb = this.createQueryBuilder(entityName, options.ctx, options.connectionType, false)
228
+ .indexHint(options.indexHint)
229
+ .comment(options.comments)
230
+ .hintComment(options.hintComments)
210
231
  .groupBy(options.groupBy)
211
232
  .having(options.having)
212
233
  .populate(options.populate ?? [])
@@ -215,7 +236,7 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
215
236
  return this.rethrow(qb.getCount());
216
237
  }
217
238
  async nativeInsert(entityName, data, options = {}) {
218
- options.convertCustomTypes ?? (options.convertCustomTypes = true);
239
+ options.convertCustomTypes ??= true;
219
240
  const meta = this.metadata.find(entityName);
220
241
  const collections = this.extractManyToMany(entityName, data);
221
242
  const pks = meta?.primaryKeys ?? [this.config.getNamingStrategy().referenceColumnName()];
@@ -235,16 +256,20 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
235
256
  return res;
236
257
  }
237
258
  async nativeInsertMany(entityName, data, options = {}) {
238
- options.processCollections ?? (options.processCollections = true);
239
- options.convertCustomTypes ?? (options.convertCustomTypes = true);
240
- const meta = this.metadata.find(entityName);
259
+ options.processCollections ??= true;
260
+ options.convertCustomTypes ??= true;
261
+ const meta = this.metadata.find(entityName)?.root;
241
262
  const collections = options.processCollections ? data.map(d => this.extractManyToMany(entityName, d)) : [];
242
263
  const pks = this.getPrimaryKeyFields(entityName);
243
264
  const set = new Set();
244
- data.forEach(row => Object.keys(row).forEach(k => set.add(k)));
265
+ data.forEach(row => core_1.Utils.keys(row).forEach(k => set.add(k)));
245
266
  const props = [...set].map(name => meta?.properties[name] ?? { name, fieldNames: [name] });
246
- const fields = core_1.Utils.flatten(props.map(prop => prop.fieldNames));
267
+ let fields = core_1.Utils.flatten(props.map(prop => prop.fieldNames));
268
+ const duplicates = core_1.Utils.findDuplicates(fields);
247
269
  const params = [];
270
+ if (duplicates.length) {
271
+ fields = core_1.Utils.unique(fields);
272
+ }
248
273
  /* istanbul ignore next */
249
274
  const tableName = meta ? this.getTableName(meta, options) : this.platform.quoteIdentifier(entityName);
250
275
  let sql = `insert into ${tableName} `;
@@ -255,29 +280,62 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
255
280
  else {
256
281
  sql += ' ' + data.map(() => `select null as ${this.platform.quoteIdentifier(pks[0])}`).join(' union all ');
257
282
  }
283
+ const addParams = (prop, row) => {
284
+ let value = row[prop.name];
285
+ if (prop.kind === core_1.ReferenceKind.EMBEDDED && prop.object) {
286
+ if (prop.array) {
287
+ for (let i = 0; i < value.length; i++) {
288
+ const item = value[i];
289
+ value[i] = this.mapDataToFieldNames(item, false, prop.embeddedProps, options.convertCustomTypes);
290
+ }
291
+ }
292
+ else {
293
+ value = this.mapDataToFieldNames(value, false, prop.embeddedProps, options.convertCustomTypes);
294
+ }
295
+ }
296
+ if (options.convertCustomTypes && prop.customType) {
297
+ params.push(prop.customType.convertToDatabaseValue(value, this.platform, { key: prop.name, mode: 'query-data' }));
298
+ return;
299
+ }
300
+ params.push(value);
301
+ };
258
302
  if (fields.length > 0 || this.platform.usesDefaultKeyword()) {
259
303
  sql += data.map(row => {
260
304
  const keys = [];
305
+ const usedDups = [];
261
306
  props.forEach(prop => {
262
307
  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]);
308
+ const param = core_1.Utils.flatten([...row[prop.name] ?? prop.fieldNames.map(() => null)]);
309
+ const key = param.map(() => '?');
310
+ prop.fieldNames.forEach((field, idx) => {
311
+ if (!duplicates.includes(field) || !usedDups.includes(field)) {
312
+ params.push(param[idx]);
313
+ keys.push(key[idx]);
314
+ usedDups.push(field);
315
+ }
316
+ });
269
317
  }
270
318
  else {
271
- params.push(row[prop.name]);
272
- keys.push('?');
319
+ const field = prop.fieldNames[0];
320
+ if (!duplicates.includes(field) || !usedDups.includes(field)) {
321
+ if (prop.customType && 'convertToDatabaseValueSQL' in prop.customType && !this.platform.isRaw(row[prop.name])) {
322
+ keys.push(prop.customType.convertToDatabaseValueSQL('?', this.platform));
323
+ }
324
+ else {
325
+ keys.push('?');
326
+ }
327
+ addParams(prop, row);
328
+ usedDups.push(field);
329
+ }
273
330
  }
274
331
  });
275
332
  return '(' + (keys.join(', ') || 'default') + ')';
276
333
  }).join(', ');
277
334
  }
278
- if (this.platform.usesReturningStatement()) {
279
- /* istanbul ignore next */
280
- const returningProps = meta.hydrateProps.filter(prop => prop.persist !== false && (prop.primary || prop.defaultRaw));
335
+ if (meta && this.platform.usesReturningStatement()) {
336
+ const returningProps = meta.props
337
+ .filter(prop => prop.persist !== false && prop.defaultRaw || prop.autoincrement || prop.generated)
338
+ .filter(prop => !(prop.name in data[0]) || core_1.Utils.isRawSql(data[0][prop.name]));
281
339
  const returningFields = core_1.Utils.flatten(returningProps.map(prop => prop.fieldNames));
282
340
  /* istanbul ignore next */
283
341
  sql += returningFields.length > 0 ? ` returning ${returningFields.map(field => this.platform.quoteIdentifier(field)).join(', ')}` : '';
@@ -289,8 +347,8 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
289
347
  pk = data.map(d => core_1.Utils.getPrimaryKeyCond(d, pks));
290
348
  }
291
349
  else {
292
- res.row ?? (res.row = {});
293
- res.rows ?? (res.rows = []);
350
+ res.row ??= {};
351
+ res.rows ??= [];
294
352
  pk = data.map((d, i) => d[pks[0]] ?? res.rows[i]?.[pks[0]]).map(d => [d]);
295
353
  res.insertId = res.insertId || res.row[pks[0]];
296
354
  }
@@ -300,7 +358,7 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
300
358
  return res;
301
359
  }
302
360
  async nativeUpdate(entityName, where, data, options = {}) {
303
- options.convertCustomTypes ?? (options.convertCustomTypes = true);
361
+ options.convertCustomTypes ??= true;
304
362
  const meta = this.metadata.find(entityName);
305
363
  const pks = this.getPrimaryKeyFields(entityName);
306
364
  const collections = this.extractManyToMany(entityName, data);
@@ -314,14 +372,27 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
314
372
  .withSchema(this.getSchemaName(meta, options));
315
373
  if (options.upsert) {
316
374
  /* istanbul ignore next */
317
- const uniqueFields = core_1.Utils.isPlainObject(where) ? Object.keys(where) : meta.primaryKeys;
318
- /* istanbul ignore next */
375
+ const uniqueFields = options.onConflictFields ?? (core_1.Utils.isPlainObject(where) ? core_1.Utils.keys(where) : meta.primaryKeys);
376
+ const returning = (0, core_1.getOnConflictReturningFields)(meta, data, uniqueFields, options);
319
377
  qb.insert(data)
320
- .onConflict(uniqueFields.map(p => meta?.properties[p]?.fieldNames[0] ?? p))
321
- .merge(Object.keys(data).filter(f => !uniqueFields.includes(f)));
378
+ .onConflict(uniqueFields)
379
+ .returning(returning);
380
+ if (!options.onConflictAction || options.onConflictAction === 'merge') {
381
+ const fields = (0, core_1.getOnConflictFields)(data, uniqueFields, options);
382
+ qb.merge(fields);
383
+ }
384
+ if (options.onConflictAction === 'ignore') {
385
+ qb.ignore();
386
+ }
322
387
  }
323
388
  else {
324
389
  qb.update(data).where(where);
390
+ // reload generated columns and version fields
391
+ const returning = [];
392
+ meta?.props
393
+ .filter(prop => (prop.generated && !prop.primary) || prop.version)
394
+ .forEach(prop => returning.push(prop.name));
395
+ qb.returning(returning);
325
396
  }
326
397
  res = await this.rethrow(qb.execute('run', false));
327
398
  }
@@ -331,23 +402,57 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
331
402
  return res;
332
403
  }
333
404
  async nativeUpdateMany(entityName, where, data, options = {}) {
334
- options.processCollections ?? (options.processCollections = true);
335
- options.convertCustomTypes ?? (options.convertCustomTypes = true);
405
+ options.processCollections ??= true;
406
+ options.convertCustomTypes ??= true;
336
407
  const meta = this.metadata.get(entityName);
337
408
  if (options.upsert) {
338
- const uniqueFields = core_1.Utils.isPlainObject(where[0]) ? Object.keys(where[0]) : meta.primaryKeys;
409
+ const uniqueFields = options.onConflictFields ?? (core_1.Utils.isPlainObject(where[0]) ? Object.keys(where[0]).flatMap(key => core_1.Utils.splitPrimaryKeys(key)) : meta.primaryKeys);
339
410
  const qb = this.createQueryBuilder(entityName, options.ctx, 'write', options.convertCustomTypes).withSchema(this.getSchemaName(meta, options));
411
+ const returning = (0, core_1.getOnConflictReturningFields)(meta, data[0], uniqueFields, options);
340
412
  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)));
343
- return qb.execute('run', false);
413
+ .onConflict(uniqueFields)
414
+ .returning(returning);
415
+ if (!options.onConflictAction || options.onConflictAction === 'merge') {
416
+ const fields = (0, core_1.getOnConflictFields)(data[0], uniqueFields, options);
417
+ qb.merge(fields);
418
+ }
419
+ if (options.onConflictAction === 'ignore') {
420
+ qb.ignore();
421
+ }
422
+ return this.rethrow(qb.execute('run', false));
344
423
  }
345
424
  const collections = options.processCollections ? data.map(d => this.extractManyToMany(entityName, d)) : [];
346
425
  const keys = new Set();
347
- data.forEach(row => Object.keys(row).forEach(k => keys.add(k)));
426
+ const returning = new Set();
427
+ data.forEach(row => {
428
+ core_1.Utils.keys(row).forEach(k => {
429
+ keys.add(k);
430
+ if (core_1.Utils.isRawSql(row[k])) {
431
+ returning.add(k);
432
+ }
433
+ });
434
+ });
435
+ // reload generated columns and version fields
436
+ meta?.props
437
+ .filter(prop => (prop.generated && !prop.primary) || prop.version)
438
+ .forEach(prop => returning.add(prop.name));
348
439
  const pkCond = core_1.Utils.flatten(meta.primaryKeys.map(pk => meta.properties[pk].fieldNames)).map(pk => `${this.platform.quoteIdentifier(pk)} = ?`).join(' and ');
349
440
  const params = [];
350
441
  let sql = `update ${this.getTableName(meta, options)} set `;
442
+ const addParams = (prop, value) => {
443
+ if (prop.kind === core_1.ReferenceKind.EMBEDDED && prop.object) {
444
+ if (prop.array) {
445
+ for (let i = 0; i < value.length; i++) {
446
+ const item = value[i];
447
+ value[i] = this.mapDataToFieldNames(item, false, prop.embeddedProps, options.convertCustomTypes);
448
+ }
449
+ }
450
+ else {
451
+ value = this.mapDataToFieldNames(value, false, prop.embeddedProps, options.convertCustomTypes);
452
+ }
453
+ }
454
+ params.push(value);
455
+ };
351
456
  keys.forEach(key => {
352
457
  const prop = meta.properties[key];
353
458
  prop.fieldNames.forEach((fieldName, fieldNameIdx) => {
@@ -362,7 +467,8 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
362
467
  else {
363
468
  sql += '?';
364
469
  }
365
- params.push(...pks, prop.fieldNames.length > 1 ? data[idx][key]?.[fieldNameIdx] : data[idx][key]);
470
+ params.push(...pks);
471
+ addParams(prop, prop.fieldNames.length > 1 ? data[idx][key]?.[fieldNameIdx] : data[idx][key]);
366
472
  }
367
473
  });
368
474
  sql += ` else ${this.platform.quoteIdentifier(fieldName)} end, `;
@@ -373,7 +479,7 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
373
479
  const versionProperty = meta.properties[meta.versionProperty];
374
480
  const quotedFieldName = this.platform.quoteIdentifier(versionProperty.fieldNames[0]);
375
481
  sql += `${quotedFieldName} = `;
376
- if (versionProperty.type.toLowerCase() === 'date') {
482
+ if (versionProperty.runtimeType === 'Date') {
377
483
  sql += this.platform.getCurrentTimestampSQL(versionProperty.length);
378
484
  }
379
485
  else {
@@ -404,6 +510,11 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
404
510
  return '?';
405
511
  });
406
512
  sql += ` in (${conds.join(', ')})`;
513
+ if (this.platform.usesReturningStatement() && returning.size > 0) {
514
+ const returningFields = core_1.Utils.flatten([...returning].map(prop => meta.properties[prop].fieldNames));
515
+ /* istanbul ignore next */
516
+ sql += returningFields.length > 0 ? ` returning ${returningFields.map(field => this.platform.quoteIdentifier(field)).join(', ')}` : '';
517
+ }
407
518
  const res = await this.rethrow(this.execute(sql, params, 'run', options.ctx));
408
519
  for (let i = 0; i < collections.length; i++) {
409
520
  await this.processManyToMany(meta, where[i], collections[i], false, options);
@@ -419,49 +530,81 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
419
530
  const qb = this.createQueryBuilder(entityName, options.ctx, 'write', false).delete(where).withSchema(this.getSchemaName(meta, options));
420
531
  return this.rethrow(qb.execute('run', false));
421
532
  }
422
- async syncCollection(coll, options) {
423
- const wrapped = (0, core_1.helper)(coll.owner);
424
- const meta = wrapped.__meta;
425
- const pks = wrapped.getPrimaryKeys(true);
426
- const snap = coll.getSnapshot();
427
- const includes = (arr, item) => !!arr.find(i => core_1.Utils.equals(i, item));
428
- const snapshot = snap ? snap.map(item => (0, core_1.helper)(item).getPrimaryKeys(true)) : [];
429
- const current = coll.getItems(false).map(item => (0, core_1.helper)(item).getPrimaryKeys(true));
430
- const deleteDiff = snap ? snapshot.filter(item => !includes(current, item)) : true;
431
- const insertDiff = current.filter(item => !includes(snapshot, item));
432
- const target = snapshot.filter(item => includes(current, item)).concat(...insertDiff);
433
- const equals = core_1.Utils.equals(current, target);
434
- const ctx = options?.ctx;
435
- // wrong order if we just delete and insert to the end (only owning sides can have fixed order)
436
- if (coll.property.owner && coll.property.fixedOrder && !equals && Array.isArray(deleteDiff)) {
437
- deleteDiff.length = insertDiff.length = 0;
438
- deleteDiff.push(...snapshot);
439
- insertDiff.push(...current);
440
- }
441
- if (coll.property.reference === core_1.ReferenceType.ONE_TO_MANY) {
442
- const cols = coll.property.referencedColumnNames;
443
- const qb = this.createQueryBuilder(coll.property.type, ctx, 'write')
444
- .withSchema(this.getSchemaName(meta, options))
445
- .update({ [coll.property.mappedBy]: pks })
446
- .getKnexQuery()
447
- .whereIn(cols, insertDiff);
448
- return this.rethrow(this.execute(qb));
449
- }
450
- /* istanbul ignore next */
451
- const ownerSchema = wrapped.getSchema() === '*' ? this.config.get('schema') : wrapped.getSchema();
452
- const pivotMeta = this.metadata.find(coll.property.pivotEntity);
453
- if (pivotMeta.schema === '*') {
533
+ async syncCollections(collections, options) {
534
+ const groups = {};
535
+ for (const coll of collections) {
536
+ const wrapped = (0, core_1.helper)(coll.owner);
537
+ const meta = wrapped.__meta;
538
+ const pks = wrapped.getPrimaryKeys(true);
539
+ const snap = coll.getSnapshot();
540
+ const includes = (arr, item) => !!arr.find(i => core_1.Utils.equals(i, item));
541
+ const snapshot = snap ? snap.map(item => (0, core_1.helper)(item).getPrimaryKeys(true)) : [];
542
+ const current = coll.getItems(false).map(item => (0, core_1.helper)(item).getPrimaryKeys(true));
543
+ const deleteDiff = snap ? snapshot.filter(item => !includes(current, item)) : true;
544
+ const insertDiff = current.filter(item => !includes(snapshot, item));
545
+ const target = snapshot.filter(item => includes(current, item)).concat(...insertDiff);
546
+ const equals = core_1.Utils.equals(current, target);
547
+ // wrong order if we just delete and insert to the end (only owning sides can have fixed order)
548
+ if (coll.property.owner && coll.property.fixedOrder && !equals && Array.isArray(deleteDiff)) {
549
+ deleteDiff.length = insertDiff.length = 0;
550
+ deleteDiff.push(...snapshot);
551
+ insertDiff.push(...current);
552
+ }
553
+ if (coll.property.kind === core_1.ReferenceKind.ONE_TO_MANY) {
554
+ const cols = coll.property.referencedColumnNames;
555
+ const qb = this.createQueryBuilder(coll.property.type, options?.ctx, 'write')
556
+ .withSchema(this.getSchemaName(meta, options));
557
+ if (coll.getSnapshot() === undefined) {
558
+ if (coll.property.orphanRemoval) {
559
+ const kqb = qb.delete({ [coll.property.mappedBy]: pks })
560
+ .getKnexQuery()
561
+ .whereNotIn(cols, insertDiff);
562
+ return this.rethrow(this.execute(kqb));
563
+ }
564
+ const kqb = qb.update({ [coll.property.mappedBy]: null })
565
+ .getKnexQuery()
566
+ .whereNotIn(cols, insertDiff);
567
+ return this.rethrow(this.execute(kqb));
568
+ }
569
+ const kqb = qb.update({ [coll.property.mappedBy]: pks })
570
+ .getKnexQuery()
571
+ .whereIn(cols, insertDiff);
572
+ return this.rethrow(this.execute(kqb));
573
+ }
454
574
  /* istanbul ignore next */
455
- options ?? (options = {});
456
- options.schema = ownerSchema;
575
+ const pivotMeta = this.metadata.find(coll.property.pivotEntity);
576
+ let schema = pivotMeta.schema;
577
+ if (schema === '*') {
578
+ const ownerSchema = wrapped.getSchema() === '*' ? this.config.get('schema') : wrapped.getSchema();
579
+ schema = coll.property.owner ? ownerSchema : this.config.get('schema');
580
+ }
581
+ else if (schema == null) {
582
+ schema = this.config.get('schema');
583
+ }
584
+ const tableName = `${schema ?? '_'}.${pivotMeta.tableName}`;
585
+ const persister = groups[tableName] ??= new PivotCollectionPersister_1.PivotCollectionPersister(pivotMeta, this, options?.ctx, schema);
586
+ persister.enqueueUpdate(coll.property, insertDiff, deleteDiff, pks);
587
+ }
588
+ for (const persister of core_1.Utils.values(groups)) {
589
+ await this.rethrow(persister.execute());
457
590
  }
458
- return this.rethrow(this.updateCollectionDiff(meta, coll.property, pks, deleteDiff, insertDiff, options));
459
591
  }
460
- async loadFromPivotTable(prop, owners, where = {}, orderBy, ctx, options) {
461
- const pivotProp2 = this.getPivotInverseProperty(prop);
462
- const ownerMeta = this.metadata.find(pivotProp2.type);
592
+ async loadFromPivotTable(prop, owners, where = {}, orderBy, ctx, options, pivotJoin) {
463
593
  const pivotMeta = this.metadata.find(prop.pivotEntity);
464
- const cond = { [`${prop.pivotEntity}.${pivotProp2.name}`]: { $in: ownerMeta.compositePK ? owners : owners.map(o => o[0]) } };
594
+ const pivotProp1 = pivotMeta.relations[prop.owner ? 1 : 0];
595
+ const pivotProp2 = pivotMeta.relations[prop.owner ? 0 : 1];
596
+ const ownerMeta = this.metadata.find(pivotProp2.type);
597
+ options = { ...options };
598
+ const qb = this.createQueryBuilder(prop.pivotEntity, ctx, options.connectionType)
599
+ .withSchema(this.getSchemaName(pivotMeta, options))
600
+ .indexHint(options.indexHint)
601
+ .comment(options.comments)
602
+ .hintComment(options.hintComments);
603
+ const pivotAlias = qb.alias;
604
+ const pivotKey = pivotProp2.joinColumns.map(column => `${pivotAlias}.${column}`).join(core_1.Utils.PK_SEPARATOR);
605
+ const cond = {
606
+ [pivotKey]: { $in: ownerMeta.compositePK ? owners : owners.map(o => o[0]) },
607
+ };
465
608
  /* istanbul ignore if */
466
609
  if (!core_1.Utils.isEmpty(where) && Object.keys(where).every(k => core_1.Utils.isOperator(k, false))) {
467
610
  where = cond;
@@ -469,29 +612,50 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
469
612
  else {
470
613
  where = { ...where, ...cond };
471
614
  }
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)) {
480
- qb.limit(options.limit, options.offset);
481
- }
482
- if (prop.targetMeta.schema !== '*' && pivotMeta.schema === '*' && options?.schema) {
483
- // eslint-disable-next-line dot-notation
484
- qb['finalize']();
485
- // eslint-disable-next-line dot-notation
486
- Object.values(qb['_joins']).forEach(join => {
487
- join.schema = options.schema;
615
+ orderBy = this.getPivotOrderBy(prop, pivotProp1, pivotAlias, orderBy);
616
+ const populate = this.autoJoinOneToOneOwner(prop.targetMeta, []);
617
+ const fields = [];
618
+ const k1 = !prop.owner ? 'joinColumns' : 'inverseJoinColumns';
619
+ const k2 = prop.owner ? 'joinColumns' : 'inverseJoinColumns';
620
+ const cols = [
621
+ ...prop[k1].map(col => `${pivotAlias}.${col} as fk__${col}`),
622
+ ...prop[k2].map(col => `${pivotAlias}.${col} as fk__${col}`),
623
+ ];
624
+ fields.push(...cols);
625
+ if (!pivotJoin) {
626
+ const targetAlias = qb.getNextAlias(prop.targetMeta.tableName);
627
+ const targetSchema = this.getSchemaName(prop.targetMeta, options) ?? this.platform.getDefaultSchemaName();
628
+ qb.innerJoin(pivotProp1.name, targetAlias, {}, targetSchema);
629
+ const targetFields = this.buildFields(prop.targetMeta, (options.populate ?? []), [], qb, targetAlias, options.fields);
630
+ for (const field of targetFields) {
631
+ const f = field.toString();
632
+ fields.unshift(f.includes('.') ? field : `${targetAlias}.${f}`);
633
+ }
634
+ // we need to handle 1:1 owner auto-joins explicitly, as the QB type is the pivot table, not the target
635
+ populate.forEach(hint => {
636
+ const alias = qb.getNextAlias(prop.targetMeta.tableName);
637
+ qb.leftJoin(`${targetAlias}.${hint.field}`, alias);
638
+ // eslint-disable-next-line dot-notation
639
+ Object.values(qb['_joins']).forEach(join => {
640
+ if (join.alias === alias && join.prop.name === hint.field) {
641
+ fields.push(...qb.helper.mapJoinColumns(qb.type, join));
642
+ }
643
+ });
488
644
  });
489
645
  }
490
- const items = owners.length ? await this.rethrow(qb.execute('all')) : [];
646
+ qb.select(fields)
647
+ .where({ [pivotProp1.name]: where })
648
+ .orderBy(orderBy)
649
+ .setLockMode(options.lockMode, options.lockTableAliases);
650
+ if (owners.length === 1 && (options.offset != null || options.limit != null)) {
651
+ qb.limit(options.limit, options.offset);
652
+ }
653
+ const res = owners.length ? await this.rethrow(qb.execute('all', { mergeResults: false, mapResults: false })) : [];
654
+ const items = res.map((row) => super.mapResult(row, prop.targetMeta));
491
655
  const map = {};
492
656
  const pkProps = ownerMeta.getPrimaryProps();
493
657
  owners.forEach(owner => {
494
- const key = core_1.Utils.getPrimaryKeyHash(prop.joinColumns.map((col, idx) => {
658
+ const key = core_1.Utils.getPrimaryKeyHash(prop.joinColumns.map((_col, idx) => {
495
659
  const pkProp = pkProps[idx];
496
660
  return pkProp.customType ? pkProp.customType.convertToJSValue(owner[idx], this.platform) : owner[idx];
497
661
  }));
@@ -504,10 +668,25 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
504
668
  }));
505
669
  map[key].push(item);
506
670
  prop.joinColumns.forEach(col => delete item[`fk__${col}`]);
507
- prop.inverseJoinColumns.forEach(col => delete item[`fk__${col}`]);
671
+ prop.inverseJoinColumns.forEach((col, idx) => {
672
+ core_1.Utils.renameKey(item, `fk__${col}`, prop.targetMeta.primaryKeys[idx]);
673
+ });
508
674
  });
509
675
  return map;
510
676
  }
677
+ getPivotOrderBy(prop, pivotProp, pivotAlias, orderBy) {
678
+ // FIXME this is ignoring the rest of the array items
679
+ if (!core_1.Utils.isEmpty(orderBy)) {
680
+ return [{ [pivotProp.name]: core_1.Utils.asArray(orderBy)[0] }];
681
+ }
682
+ if (!core_1.Utils.isEmpty(prop.orderBy)) {
683
+ return [{ [pivotProp.name]: core_1.Utils.asArray(prop.orderBy)[0] }];
684
+ }
685
+ if (prop.fixedOrder) {
686
+ return [{ [`${pivotAlias}.${prop.fixedOrderColumn}`]: core_1.QueryOrder.ASC }];
687
+ }
688
+ return [];
689
+ }
511
690
  async execute(queryOrKnex, params = [], method = 'all', ctx) {
512
691
  return this.rethrow(this.connection.execute(queryOrKnex, params, method, ctx));
513
692
  }
@@ -520,34 +699,51 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
520
699
  }
521
700
  const relationsToPopulate = populate.map(({ field }) => field);
522
701
  const toPopulate = meta.relations
523
- .filter(prop => prop.reference === core_1.ReferenceType.ONE_TO_ONE && !prop.owner && !relationsToPopulate.includes(prop.name))
702
+ .filter(prop => prop.kind === core_1.ReferenceKind.ONE_TO_ONE && !prop.owner && !relationsToPopulate.includes(prop.name))
524
703
  .filter(prop => fields.length === 0 || fields.some(f => prop.name === f || prop.name.startsWith(`${String(f)}.`)))
525
704
  .map(prop => ({ field: prop.name, strategy: prop.strategy }));
526
705
  return [...populate, ...toPopulate];
527
706
  }
707
+ /**
708
+ * @internal
709
+ */
528
710
  joinedProps(meta, populate) {
529
711
  return populate.filter(p => {
530
712
  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;
713
+ return (p.strategy || prop.strategy || this.config.get('loadStrategy')) === core_1.LoadStrategy.JOINED && ![core_1.ReferenceKind.SCALAR, core_1.ReferenceKind.EMBEDDED].includes(prop.kind);
532
714
  });
533
715
  }
534
716
  /**
535
717
  * @internal
536
718
  */
537
- mergeJoinedResult(rawResults, meta) {
538
- // group by the root entity primary key first
539
- const map = {};
719
+ mergeJoinedResult(rawResults, meta, joinedProps) {
540
720
  const res = [];
541
- rawResults.forEach(item => {
721
+ const map = {};
722
+ for (const item of rawResults) {
542
723
  const pk = core_1.Utils.getCompositeKeyHash(item, meta);
543
724
  if (map[pk]) {
544
- map[pk].push(item);
725
+ for (const hint of joinedProps) {
726
+ const prop = meta.properties[hint.field];
727
+ if (!item[hint.field]) {
728
+ continue;
729
+ }
730
+ switch (prop.kind) {
731
+ case core_1.ReferenceKind.ONE_TO_MANY:
732
+ case core_1.ReferenceKind.MANY_TO_MANY:
733
+ map[pk][hint.field] = this.mergeJoinedResult([...map[pk][hint.field], ...item[hint.field]], prop.targetMeta, hint.children ?? []);
734
+ break;
735
+ case core_1.ReferenceKind.MANY_TO_ONE:
736
+ case core_1.ReferenceKind.ONE_TO_ONE:
737
+ map[pk][hint.field] = this.mergeJoinedResult([map[pk][hint.field], item[hint.field]], prop.targetMeta, hint.children ?? [])[0];
738
+ break;
739
+ }
740
+ }
545
741
  }
546
742
  else {
547
- map[pk] = [item];
743
+ map[pk] = item;
548
744
  res.push(item);
549
745
  }
550
- });
746
+ }
551
747
  return res;
552
748
  }
553
749
  getFieldsForJoinedLoad(qb, meta, explicitFields, populate = [], parentTableAlias, parentJoinPath) {
@@ -563,19 +759,20 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
563
759
  if (!fields || prop.primary) {
564
760
  return !fields?.includes('*');
565
761
  }
566
- return fields.includes(prop.name);
762
+ return fields.some(f => f === prop.name || f.toString().startsWith(prop.name + '.'));
567
763
  };
568
764
  // alias all fields in the primary table
569
765
  meta.props
570
766
  .filter(prop => shouldHaveColumn(prop, populate, explicitFields))
571
767
  .forEach(prop => fields.push(...this.mapPropToFieldNames(qb, prop, parentTableAlias)));
572
768
  joinedProps.forEach(relation => {
573
- const prop = meta.properties[relation.field];
769
+ const [propName] = relation.field.split(':', 2);
770
+ const prop = meta.properties[propName];
574
771
  const meta2 = this.metadata.find(prop.type);
575
772
  const tableAlias = qb.getNextAlias(prop.name);
576
773
  const field = parentTableAlias ? `${parentTableAlias}.${prop.name}` : prop.name;
577
774
  const path = parentJoinPath ? `${parentJoinPath}.${prop.name}` : `${meta.name}.${prop.name}`;
578
- qb.join(field, tableAlias, {}, 'leftJoin', path);
775
+ qb.join(field, tableAlias, {}, query_1.JoinType.leftJoin, path);
579
776
  const childExplicitFields = explicitFields?.filter(f => core_1.Utils.isPlainObject(f)).map(o => o[prop.name])[0] || [];
580
777
  explicitFields?.forEach(f => {
581
778
  if (typeof f === 'string' && f.startsWith(`${prop.name}.`)) {
@@ -590,24 +787,25 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
590
787
  * @internal
591
788
  */
592
789
  mapPropToFieldNames(qb, prop, tableAlias) {
593
- const aliased = qb.ref(tableAlias ? `${tableAlias}__${prop.fieldNames[0]}` : prop.fieldNames[0]).toString();
790
+ const knex = this.connection.getKnex();
791
+ const aliased = knex.ref(tableAlias ? `${tableAlias}__${prop.fieldNames[0]}` : prop.fieldNames[0]).toString();
594
792
  if (prop.customType?.convertToJSValueSQL && tableAlias) {
595
- const prefixed = qb.ref(prop.fieldNames[0]).withSchema(tableAlias).toString();
596
- return [qb.raw(prop.customType.convertToJSValueSQL(prefixed, this.platform) + ' as ' + aliased).toString()];
793
+ const prefixed = knex.ref(prop.fieldNames[0]).withSchema(tableAlias).toString();
794
+ return [(0, core_1.raw)(`${prop.customType.convertToJSValueSQL(prefixed, this.platform)} as ${aliased}`)];
597
795
  }
598
796
  if (prop.formula) {
599
- const alias = qb.ref(tableAlias ?? qb.alias).toString();
600
- return [`${prop.formula(alias)} as ${aliased}`];
797
+ const alias = knex.ref(tableAlias ?? qb.alias).toString();
798
+ return [(0, core_1.raw)(`${prop.formula(alias)} as ${aliased}`)];
601
799
  }
602
800
  if (tableAlias) {
603
- return prop.fieldNames.map(fieldName => qb.ref(fieldName).withSchema(tableAlias).as(`${tableAlias}__${fieldName}`));
801
+ return prop.fieldNames.map(fieldName => knex.ref(fieldName).withSchema(tableAlias).as(`${tableAlias}__${fieldName}`));
604
802
  }
605
803
  return prop.fieldNames;
606
804
  }
607
805
  /** @internal */
608
- createQueryBuilder(entityName, ctx, preferredConnectionType, convertCustomTypes) {
806
+ createQueryBuilder(entityName, ctx, preferredConnectionType, convertCustomTypes, logging) {
609
807
  const connectionType = this.resolveConnectionType({ ctx, connectionType: preferredConnectionType });
610
- const qb = new query_1.QueryBuilder(entityName, this.metadata, this, ctx, undefined, connectionType);
808
+ const qb = new query_1.QueryBuilder(entityName, this.metadata, this, ctx, undefined, connectionType, undefined, logging);
611
809
  if (!convertCustomTypes) {
612
810
  qb.unsetFlag(core_1.QueryFlag.CONVERT_CUSTOM_TYPES);
613
811
  }
@@ -631,7 +829,7 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
631
829
  }
632
830
  const ret = {};
633
831
  this.metadata.find(entityName).relations.forEach(prop => {
634
- if (prop.reference === core_1.ReferenceType.MANY_TO_MANY && data[prop.name]) {
832
+ if (prop.kind === core_1.ReferenceKind.MANY_TO_MANY && data[prop.name]) {
635
833
  ret[prop.name] = data[prop.name].map((item) => core_1.Utils.asArray(item));
636
834
  delete data[prop.name];
637
835
  }
@@ -644,65 +842,31 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
644
842
  }
645
843
  for (const prop of meta.relations) {
646
844
  if (collections[prop.name]) {
647
- await this.rethrow(this.updateCollectionDiff(meta, prop, pks, clear, collections[prop.name], options));
845
+ const pivotMeta = this.metadata.find(prop.pivotEntity);
846
+ const persister = new PivotCollectionPersister_1.PivotCollectionPersister(pivotMeta, this, options?.ctx, options?.schema);
847
+ persister.enqueueUpdate(prop, collections[prop.name], clear, pks);
848
+ await this.rethrow(persister.execute());
648
849
  }
649
850
  }
650
851
  }
651
- async updateCollectionDiff(meta, prop, pks, deleteDiff, insertDiff, options) {
652
- if (!deleteDiff) {
653
- deleteDiff = [];
654
- }
655
- if (deleteDiff === true || deleteDiff.length > 0) {
656
- const qb1 = this.createQueryBuilder(prop.pivotEntity, options?.ctx, 'write')
657
- .withSchema(this.getSchemaName(meta, options))
658
- .unsetFlag(core_1.QueryFlag.CONVERT_CUSTOM_TYPES);
659
- const knex = qb1.getKnex();
660
- if (Array.isArray(deleteDiff)) {
661
- knex.whereIn(prop.inverseJoinColumns, deleteDiff);
662
- }
663
- prop.joinColumns.forEach((joinColumn, idx) => knex.andWhere(joinColumn, pks[idx]));
664
- await this.execute(knex.delete());
665
- }
666
- if (insertDiff.length === 0) {
667
- return;
668
- }
669
- const items = insertDiff.map(item => {
670
- const cond = {};
671
- prop.joinColumns.forEach((joinColumn, idx) => cond[joinColumn] = pks[idx]);
672
- prop.inverseJoinColumns.forEach((inverseJoinColumn, idx) => cond[inverseJoinColumn] = item[idx]);
673
- return cond;
674
- });
675
- /* istanbul ignore else */
676
- if (this.platform.allowsMultiInsert()) {
677
- await this.nativeInsertMany(prop.pivotEntity, items, { ...options, convertCustomTypes: false, processCollections: false });
678
- }
679
- else {
680
- await core_1.Utils.runSerial(items, item => {
681
- return this.createQueryBuilder(prop.pivotEntity, options?.ctx, 'write')
682
- .unsetFlag(core_1.QueryFlag.CONVERT_CUSTOM_TYPES)
683
- .withSchema(this.getSchemaName(meta, options))
684
- .insert(item)
685
- .execute('run', false);
686
- });
687
- }
688
- }
689
852
  async lockPessimistic(entity, options) {
690
853
  const meta = (0, core_1.helper)(entity).__meta;
691
- const qb = this.createQueryBuilder(entity.constructor.name, options.ctx).unsetFlag(core_1.QueryFlag.CONVERT_CUSTOM_TYPES).withSchema(options.schema ?? meta.schema);
854
+ const qb = this.createQueryBuilder(entity.constructor.name, options.ctx).withSchema(options.schema ?? meta.schema);
692
855
  const cond = core_1.Utils.getPrimaryKeyCond(entity, meta.primaryKeys);
693
- qb.select('1').where(cond).setLockMode(options.lockMode, options.lockTableAliases);
856
+ qb.select((0, core_1.raw)('1')).where(cond).setLockMode(options.lockMode, options.lockTableAliases);
694
857
  await this.rethrow(qb.execute());
695
858
  }
696
859
  buildJoinedPropsOrderBy(entityName, qb, meta, populate, parentPath) {
697
860
  const orderBy = [];
698
861
  const joinedProps = this.joinedProps(meta, populate);
699
862
  joinedProps.forEach(relation => {
700
- const prop = meta.properties[relation.field];
863
+ const [propName] = relation.field.split(':', 2);
864
+ const prop = meta.properties[propName];
701
865
  const propOrderBy = prop.orderBy;
702
866
  const path = `${parentPath ? parentPath : entityName}.${relation.field}`;
703
867
  const propAlias = qb.getAliasForJoinPath(path);
704
868
  if (propOrderBy) {
705
- Object.keys(propOrderBy).forEach(field => {
869
+ core_1.Utils.keys(propOrderBy).forEach(field => {
706
870
  orderBy.push({ [`${propAlias}.${field}`]: propOrderBy[field] });
707
871
  });
708
872
  }
@@ -729,12 +893,12 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
729
893
  return ret;
730
894
  }
731
895
  processField(meta, prop, field, ret, populate, joinedProps, qb) {
732
- if (!prop || (prop.reference === core_1.ReferenceType.ONE_TO_ONE && !prop.owner)) {
896
+ if (!prop || (prop.kind === core_1.ReferenceKind.ONE_TO_ONE && !prop.owner)) {
733
897
  return;
734
898
  }
735
- if (prop.reference === core_1.ReferenceType.EMBEDDED) {
899
+ if (prop.kind === core_1.ReferenceKind.EMBEDDED) {
736
900
  if (prop.object) {
737
- ret.push(prop.fieldNames[0]);
901
+ ret.push(prop.name);
738
902
  return;
739
903
  }
740
904
  const parts = field.split('.');
@@ -746,16 +910,15 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
746
910
  }
747
911
  return;
748
912
  }
749
- if (prop.fieldNames) {
750
- ret.push(prop.fieldNames[0]);
751
- }
913
+ ret.push(prop.name);
752
914
  }
753
- buildFields(meta, populate, joinedProps, qb, fields) {
915
+ buildFields(meta, populate, joinedProps, qb, alias, fields) {
754
916
  const lazyProps = meta.props.filter(prop => prop.lazy && !populate.some(p => p.field === prop.name || p.all));
755
917
  const hasLazyFormulas = meta.props.some(p => p.lazy && p.formula);
756
- const requiresSQLConversion = meta.props.some(p => p.customType?.convertToJSValueSQL);
918
+ const requiresSQLConversion = meta.props.some(p => p.customType?.convertToJSValueSQL && p.persist !== false);
757
919
  const hasExplicitFields = !!fields;
758
920
  const ret = [];
921
+ let addFormulas = false;
759
922
  if (joinedProps.length > 0) {
760
923
  ret.push(...this.getFieldsForJoinedLoad(qb, meta, fields, populate));
761
924
  }
@@ -781,20 +944,22 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
781
944
  else if (lazyProps.filter(p => !p.formula).length > 0) {
782
945
  const props = meta.props.filter(prop => this.platform.shouldHaveColumn(prop, populate, false));
783
946
  ret.push(...core_1.Utils.flatten(props.filter(p => !lazyProps.includes(p)).map(p => p.fieldNames)));
947
+ addFormulas = true;
784
948
  }
785
949
  else if (hasLazyFormulas || requiresSQLConversion) {
786
950
  ret.push('*');
951
+ addFormulas = true;
787
952
  }
788
- if (ret.length > 0 && !hasExplicitFields) {
953
+ if (ret.length > 0 && !hasExplicitFields && addFormulas) {
789
954
  meta.props
790
955
  .filter(prop => prop.formula && !lazyProps.includes(prop))
791
956
  .forEach(prop => {
792
- const alias = qb.ref(qb.alias).toString();
793
- const aliased = qb.ref(prop.fieldNames[0]).toString();
794
- ret.push(`${prop.formula(alias)} as ${aliased}`);
957
+ const a = this.connection.getKnex().ref(alias).toString();
958
+ const aliased = this.connection.getKnex().ref(prop.fieldNames[0]).toString();
959
+ ret.push((0, core_1.raw)(`${prop.formula(a)} as ${aliased}`));
795
960
  });
796
961
  meta.props
797
- .filter(prop => prop.customType?.convertToDatabaseValueSQL || prop.customType?.convertToJSValueSQL)
962
+ .filter(prop => prop.hasConvertToDatabaseValueSQL || prop.hasConvertToJSValueSQL)
798
963
  .forEach(prop => ret.push(prop.name));
799
964
  }
800
965
  return ret.length > 0 ? core_1.Utils.unique(ret) : ['*'];