@mikro-orm/knex 6.0.0-dev.12 → 6.0.0-dev.121

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 (41) 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 +378 -221
  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 +23 -6
  17. package/package.json +8 -8
  18. package/query/ArrayCriteriaNode.d.ts +3 -3
  19. package/query/CriteriaNode.d.ts +8 -8
  20. package/query/CriteriaNode.js +16 -9
  21. package/query/CriteriaNodeFactory.d.ts +6 -6
  22. package/query/CriteriaNodeFactory.js +17 -13
  23. package/query/ObjectCriteriaNode.d.ts +3 -3
  24. package/query/ObjectCriteriaNode.js +12 -8
  25. package/query/QueryBuilder.d.ts +44 -20
  26. package/query/QueryBuilder.js +371 -130
  27. package/query/QueryBuilderHelper.d.ts +10 -15
  28. package/query/QueryBuilderHelper.js +197 -158
  29. package/query/ScalarCriteriaNode.d.ts +3 -2
  30. package/query/ScalarCriteriaNode.js +9 -6
  31. package/query/enums.js +1 -1
  32. package/schema/DatabaseSchema.d.ts +5 -2
  33. package/schema/DatabaseSchema.js +27 -9
  34. package/schema/DatabaseTable.d.ts +4 -2
  35. package/schema/DatabaseTable.js +66 -32
  36. package/schema/SchemaComparator.js +15 -3
  37. package/schema/SchemaHelper.d.ts +6 -4
  38. package/schema/SchemaHelper.js +11 -4
  39. package/schema/SqlSchemaGenerator.d.ts +3 -6
  40. package/schema/SqlSchemaGenerator.js +61 -28
  41. package/typings.d.ts +13 -7
@@ -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,57 @@ 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);
120
+ .limit(options?.limit, options?.offset)
121
+ .indexHint(options.indexHint)
122
+ .comment(options.comments)
123
+ .hintComment(options.hintComments);
109
124
  if (options.orderBy) {
110
125
  qb.orderBy(options.orderBy);
111
126
  }
112
127
  qb.where(where);
113
128
  const kqb = qb.getKnexQuery().clear('select');
114
129
  if (type === query_1.QueryType.COUNT) {
115
- kqb.select(qb.raw('count(*) as count'));
130
+ kqb.select(this.connection.getKnex().raw('count(*) as count'));
116
131
  }
117
132
  else { // select
118
133
  kqb.select('*');
@@ -131,6 +146,7 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
131
146
  return null;
132
147
  }
133
148
  if (qb) {
149
+ // here we map the aliased results (cartesian product) to an object graph
134
150
  this.mapJoinedProps(ret, meta, populate, qb, ret, map);
135
151
  }
136
152
  return ret;
@@ -153,12 +169,26 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
153
169
  return root[`${relationAlias}__${name}`] != null;
154
170
  }));
155
171
  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] || [];
172
+ if ([core_1.ReferenceKind.MANY_TO_MANY, core_1.ReferenceKind.ONE_TO_MANY].includes(relation.kind)) {
173
+ result[relation.name] ??= [];
174
+ }
175
+ if ([core_1.ReferenceKind.MANY_TO_ONE, core_1.ReferenceKind.ONE_TO_ONE].includes(relation.kind)) {
176
+ result[relation.name] = null;
159
177
  }
160
178
  return;
161
179
  }
180
+ const tz = this.platform.getTimezone();
181
+ meta2.props
182
+ .filter(prop => prop.persist === false && prop.fieldNames)
183
+ .forEach(prop => {
184
+ if (prop.fieldNames.length > 1) { // composite keys
185
+ relationPojo[prop.name] = prop.fieldNames.map(name => root[`${relationAlias}__${name}`]);
186
+ }
187
+ else {
188
+ const alias = `${relationAlias}__${prop.fieldNames[0]}`;
189
+ relationPojo[prop.name] = root[alias];
190
+ }
191
+ });
162
192
  meta2.props
163
193
  .filter(prop => this.platform.shouldHaveColumn(prop, p.children || []))
164
194
  .forEach(prop => {
@@ -166,22 +196,20 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
166
196
  relationPojo[prop.name] = prop.fieldNames.map(name => root[`${relationAlias}__${name}`]);
167
197
  prop.fieldNames.map(name => delete root[`${relationAlias}__${name}`]);
168
198
  }
199
+ else if (prop.runtimeType === 'Date') {
200
+ const alias = `${relationAlias}__${prop.fieldNames[0]}`;
201
+ relationPojo[prop.name] = (typeof root[alias] === 'string' ? new Date(root[alias]) : root[alias]);
202
+ delete root[alias];
203
+ }
169
204
  else {
170
205
  const alias = `${relationAlias}__${prop.fieldNames[0]}`;
171
206
  relationPojo[prop.name] = root[alias];
172
207
  delete root[alias];
173
208
  }
174
209
  });
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);
210
+ if ([core_1.ReferenceKind.MANY_TO_MANY, core_1.ReferenceKind.ONE_TO_MANY].includes(relation.kind)) {
211
+ result[relation.name] ??= [];
212
+ result[relation.name].push(relationPojo);
185
213
  }
186
214
  else {
187
215
  result[relation.name] = relationPojo;
@@ -190,23 +218,15 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
190
218
  this.mapJoinedProps(relationPojo, meta2, populateChildren, qb, root, map, path);
191
219
  });
192
220
  }
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
221
  async count(entityName, where, options = {}) {
205
222
  const meta = this.metadata.find(entityName);
206
223
  if (meta?.virtual) {
207
224
  return this.countVirtual(entityName, where, options);
208
225
  }
209
226
  const qb = this.createQueryBuilder(entityName, options.ctx, options.connectionType, false)
227
+ .indexHint(options.indexHint)
228
+ .comment(options.comments)
229
+ .hintComment(options.hintComments)
210
230
  .groupBy(options.groupBy)
211
231
  .having(options.having)
212
232
  .populate(options.populate ?? [])
@@ -215,7 +235,7 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
215
235
  return this.rethrow(qb.getCount());
216
236
  }
217
237
  async nativeInsert(entityName, data, options = {}) {
218
- options.convertCustomTypes ?? (options.convertCustomTypes = true);
238
+ options.convertCustomTypes ??= true;
219
239
  const meta = this.metadata.find(entityName);
220
240
  const collections = this.extractManyToMany(entityName, data);
221
241
  const pks = meta?.primaryKeys ?? [this.config.getNamingStrategy().referenceColumnName()];
@@ -235,16 +255,20 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
235
255
  return res;
236
256
  }
237
257
  async nativeInsertMany(entityName, data, options = {}) {
238
- options.processCollections ?? (options.processCollections = true);
239
- options.convertCustomTypes ?? (options.convertCustomTypes = true);
240
- const meta = this.metadata.find(entityName);
258
+ options.processCollections ??= true;
259
+ options.convertCustomTypes ??= true;
260
+ const meta = this.metadata.find(entityName)?.root;
241
261
  const collections = options.processCollections ? data.map(d => this.extractManyToMany(entityName, d)) : [];
242
262
  const pks = this.getPrimaryKeyFields(entityName);
243
263
  const set = new Set();
244
- data.forEach(row => Object.keys(row).forEach(k => set.add(k)));
264
+ data.forEach(row => core_1.Utils.keys(row).forEach(k => set.add(k)));
245
265
  const props = [...set].map(name => meta?.properties[name] ?? { name, fieldNames: [name] });
246
- const fields = core_1.Utils.flatten(props.map(prop => prop.fieldNames));
266
+ let fields = core_1.Utils.flatten(props.map(prop => prop.fieldNames));
267
+ const duplicates = core_1.Utils.findDuplicates(fields);
247
268
  const params = [];
269
+ if (duplicates.length) {
270
+ fields = core_1.Utils.unique(fields);
271
+ }
248
272
  /* istanbul ignore next */
249
273
  const tableName = meta ? this.getTableName(meta, options) : this.platform.quoteIdentifier(entityName);
250
274
  let sql = `insert into ${tableName} `;
@@ -255,29 +279,62 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
255
279
  else {
256
280
  sql += ' ' + data.map(() => `select null as ${this.platform.quoteIdentifier(pks[0])}`).join(' union all ');
257
281
  }
282
+ const addParams = (prop, row) => {
283
+ let value = row[prop.name];
284
+ if (prop.kind === core_1.ReferenceKind.EMBEDDED && prop.object) {
285
+ if (prop.array) {
286
+ for (let i = 0; i < value.length; i++) {
287
+ const item = value[i];
288
+ value[i] = this.mapDataToFieldNames(item, false, prop.embeddedProps, options.convertCustomTypes);
289
+ }
290
+ }
291
+ else {
292
+ value = this.mapDataToFieldNames(value, false, prop.embeddedProps, options.convertCustomTypes);
293
+ }
294
+ }
295
+ if (options.convertCustomTypes && prop.customType) {
296
+ params.push(prop.customType.convertToDatabaseValue(value, this.platform, { key: prop.name, mode: 'query-data' }));
297
+ return;
298
+ }
299
+ params.push(value);
300
+ };
258
301
  if (fields.length > 0 || this.platform.usesDefaultKeyword()) {
259
302
  sql += data.map(row => {
260
303
  const keys = [];
304
+ const usedDups = [];
261
305
  props.forEach(prop => {
262
306
  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]);
307
+ const param = core_1.Utils.flatten([...row[prop.name] ?? prop.fieldNames.map(() => null)]);
308
+ const key = param.map(() => '?');
309
+ prop.fieldNames.forEach((field, idx) => {
310
+ if (!duplicates.includes(field) || !usedDups.includes(field)) {
311
+ params.push(param[idx]);
312
+ keys.push(key[idx]);
313
+ usedDups.push(field);
314
+ }
315
+ });
269
316
  }
270
317
  else {
271
- params.push(row[prop.name]);
272
- keys.push('?');
318
+ const field = prop.fieldNames[0];
319
+ if (!duplicates.includes(field) || !usedDups.includes(field)) {
320
+ if (prop.customType && 'convertToDatabaseValueSQL' in prop.customType && !this.platform.isRaw(row[prop.name])) {
321
+ keys.push(prop.customType.convertToDatabaseValueSQL('?', this.platform));
322
+ }
323
+ else {
324
+ keys.push('?');
325
+ }
326
+ addParams(prop, row);
327
+ usedDups.push(field);
328
+ }
273
329
  }
274
330
  });
275
331
  return '(' + (keys.join(', ') || 'default') + ')';
276
332
  }).join(', ');
277
333
  }
278
- if (this.platform.usesReturningStatement()) {
279
- /* istanbul ignore next */
280
- const returningProps = meta.hydrateProps.filter(prop => prop.persist !== false && (prop.primary || prop.defaultRaw));
334
+ if (meta && this.platform.usesReturningStatement()) {
335
+ const returningProps = meta.props
336
+ .filter(prop => prop.persist !== false && ((prop.primary && prop.autoincrement) || prop.defaultRaw || prop.autoincrement))
337
+ .filter(prop => !(prop.name in data[0]) || core_1.Utils.isRawSql(data[0][prop.name]));
281
338
  const returningFields = core_1.Utils.flatten(returningProps.map(prop => prop.fieldNames));
282
339
  /* istanbul ignore next */
283
340
  sql += returningFields.length > 0 ? ` returning ${returningFields.map(field => this.platform.quoteIdentifier(field)).join(', ')}` : '';
@@ -289,8 +346,8 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
289
346
  pk = data.map(d => core_1.Utils.getPrimaryKeyCond(d, pks));
290
347
  }
291
348
  else {
292
- res.row ?? (res.row = {});
293
- res.rows ?? (res.rows = []);
349
+ res.row ??= {};
350
+ res.rows ??= [];
294
351
  pk = data.map((d, i) => d[pks[0]] ?? res.rows[i]?.[pks[0]]).map(d => [d]);
295
352
  res.insertId = res.insertId || res.row[pks[0]];
296
353
  }
@@ -300,7 +357,7 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
300
357
  return res;
301
358
  }
302
359
  async nativeUpdate(entityName, where, data, options = {}) {
303
- options.convertCustomTypes ?? (options.convertCustomTypes = true);
360
+ options.convertCustomTypes ??= true;
304
361
  const meta = this.metadata.find(entityName);
305
362
  const pks = this.getPrimaryKeyFields(entityName);
306
363
  const collections = this.extractManyToMany(entityName, data);
@@ -314,11 +371,18 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
314
371
  .withSchema(this.getSchemaName(meta, options));
315
372
  if (options.upsert) {
316
373
  /* istanbul ignore next */
317
- const uniqueFields = core_1.Utils.isPlainObject(where) ? Object.keys(where) : meta.primaryKeys;
318
- /* istanbul ignore next */
374
+ const uniqueFields = options.onConflictFields ?? (core_1.Utils.isPlainObject(where) ? core_1.Utils.keys(where) : meta.primaryKeys);
375
+ const returning = (0, core_1.getOnConflictReturningFields)(meta, data, uniqueFields, options);
319
376
  qb.insert(data)
320
- .onConflict(uniqueFields.map(p => meta?.properties[p]?.fieldNames[0] ?? p))
321
- .merge(Object.keys(data).filter(f => !uniqueFields.includes(f)));
377
+ .onConflict(uniqueFields)
378
+ .returning(returning);
379
+ if (!options.onConflictAction || options.onConflictAction === 'merge') {
380
+ const fields = (0, core_1.getOnConflictFields)(data, uniqueFields, options);
381
+ qb.merge(fields);
382
+ }
383
+ if (options.onConflictAction === 'ignore') {
384
+ qb.ignore();
385
+ }
322
386
  }
323
387
  else {
324
388
  qb.update(data).where(where);
@@ -331,23 +395,53 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
331
395
  return res;
332
396
  }
333
397
  async nativeUpdateMany(entityName, where, data, options = {}) {
334
- options.processCollections ?? (options.processCollections = true);
335
- options.convertCustomTypes ?? (options.convertCustomTypes = true);
398
+ options.processCollections ??= true;
399
+ options.convertCustomTypes ??= true;
336
400
  const meta = this.metadata.get(entityName);
337
401
  if (options.upsert) {
338
- const uniqueFields = core_1.Utils.isPlainObject(where[0]) ? Object.keys(where[0]) : meta.primaryKeys;
402
+ const uniqueFields = options.onConflictFields ?? (core_1.Utils.isPlainObject(where[0]) ? Object.keys(where[0]) : meta.primaryKeys);
339
403
  const qb = this.createQueryBuilder(entityName, options.ctx, 'write', options.convertCustomTypes).withSchema(this.getSchemaName(meta, options));
404
+ const returning = (0, core_1.getOnConflictReturningFields)(meta, data[0], uniqueFields, options);
340
405
  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)));
406
+ .onConflict(uniqueFields)
407
+ .returning(returning);
408
+ if (!options.onConflictAction || options.onConflictAction === 'merge') {
409
+ const fields = (0, core_1.getOnConflictFields)(data[0], uniqueFields, options);
410
+ qb.merge(fields);
411
+ }
412
+ if (options.onConflictAction === 'ignore') {
413
+ qb.ignore();
414
+ }
343
415
  return qb.execute('run', false);
344
416
  }
345
417
  const collections = options.processCollections ? data.map(d => this.extractManyToMany(entityName, d)) : [];
346
418
  const keys = new Set();
347
- data.forEach(row => Object.keys(row).forEach(k => keys.add(k)));
419
+ const returning = new Set();
420
+ data.forEach(row => {
421
+ core_1.Utils.keys(row).forEach(k => {
422
+ keys.add(k);
423
+ if (core_1.Utils.isRawSql(row[k])) {
424
+ returning.add(k);
425
+ }
426
+ });
427
+ });
348
428
  const pkCond = core_1.Utils.flatten(meta.primaryKeys.map(pk => meta.properties[pk].fieldNames)).map(pk => `${this.platform.quoteIdentifier(pk)} = ?`).join(' and ');
349
429
  const params = [];
350
430
  let sql = `update ${this.getTableName(meta, options)} set `;
431
+ const addParams = (prop, value) => {
432
+ if (prop.kind === core_1.ReferenceKind.EMBEDDED && prop.object) {
433
+ if (prop.array) {
434
+ for (let i = 0; i < value.length; i++) {
435
+ const item = value[i];
436
+ value[i] = this.mapDataToFieldNames(item, false, prop.embeddedProps, options.convertCustomTypes);
437
+ }
438
+ }
439
+ else {
440
+ value = this.mapDataToFieldNames(value, false, prop.embeddedProps, options.convertCustomTypes);
441
+ }
442
+ }
443
+ params.push(value);
444
+ };
351
445
  keys.forEach(key => {
352
446
  const prop = meta.properties[key];
353
447
  prop.fieldNames.forEach((fieldName, fieldNameIdx) => {
@@ -362,7 +456,8 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
362
456
  else {
363
457
  sql += '?';
364
458
  }
365
- params.push(...pks, prop.fieldNames.length > 1 ? data[idx][key]?.[fieldNameIdx] : data[idx][key]);
459
+ params.push(...pks);
460
+ addParams(prop, prop.fieldNames.length > 1 ? data[idx][key]?.[fieldNameIdx] : data[idx][key]);
366
461
  }
367
462
  });
368
463
  sql += ` else ${this.platform.quoteIdentifier(fieldName)} end, `;
@@ -373,7 +468,7 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
373
468
  const versionProperty = meta.properties[meta.versionProperty];
374
469
  const quotedFieldName = this.platform.quoteIdentifier(versionProperty.fieldNames[0]);
375
470
  sql += `${quotedFieldName} = `;
376
- if (versionProperty.type.toLowerCase() === 'date') {
471
+ if (versionProperty.runtimeType === 'Date') {
377
472
  sql += this.platform.getCurrentTimestampSQL(versionProperty.length);
378
473
  }
379
474
  else {
@@ -404,6 +499,11 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
404
499
  return '?';
405
500
  });
406
501
  sql += ` in (${conds.join(', ')})`;
502
+ if (this.platform.usesReturningStatement() && returning.size > 0) {
503
+ const returningFields = core_1.Utils.flatten([...returning].map(prop => meta.properties[prop].fieldNames));
504
+ /* istanbul ignore next */
505
+ sql += returningFields.length > 0 ? ` returning ${returningFields.map(field => this.platform.quoteIdentifier(field)).join(', ')}` : '';
506
+ }
407
507
  const res = await this.rethrow(this.execute(sql, params, 'run', options.ctx));
408
508
  for (let i = 0; i < collections.length; i++) {
409
509
  await this.processManyToMany(meta, where[i], collections[i], false, options);
@@ -419,49 +519,81 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
419
519
  const qb = this.createQueryBuilder(entityName, options.ctx, 'write', false).delete(where).withSchema(this.getSchemaName(meta, options));
420
520
  return this.rethrow(qb.execute('run', false));
421
521
  }
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 === '*') {
522
+ async syncCollections(collections, options) {
523
+ const groups = {};
524
+ for (const coll of collections) {
525
+ const wrapped = (0, core_1.helper)(coll.owner);
526
+ const meta = wrapped.__meta;
527
+ const pks = wrapped.getPrimaryKeys(true);
528
+ const snap = coll.getSnapshot();
529
+ const includes = (arr, item) => !!arr.find(i => core_1.Utils.equals(i, item));
530
+ const snapshot = snap ? snap.map(item => (0, core_1.helper)(item).getPrimaryKeys(true)) : [];
531
+ const current = coll.getItems(false).map(item => (0, core_1.helper)(item).getPrimaryKeys(true));
532
+ const deleteDiff = snap ? snapshot.filter(item => !includes(current, item)) : true;
533
+ const insertDiff = current.filter(item => !includes(snapshot, item));
534
+ const target = snapshot.filter(item => includes(current, item)).concat(...insertDiff);
535
+ const equals = core_1.Utils.equals(current, target);
536
+ // wrong order if we just delete and insert to the end (only owning sides can have fixed order)
537
+ if (coll.property.owner && coll.property.fixedOrder && !equals && Array.isArray(deleteDiff)) {
538
+ deleteDiff.length = insertDiff.length = 0;
539
+ deleteDiff.push(...snapshot);
540
+ insertDiff.push(...current);
541
+ }
542
+ if (coll.property.kind === core_1.ReferenceKind.ONE_TO_MANY) {
543
+ const cols = coll.property.referencedColumnNames;
544
+ const qb = this.createQueryBuilder(coll.property.type, options?.ctx, 'write')
545
+ .withSchema(this.getSchemaName(meta, options));
546
+ if (coll.getSnapshot() === undefined) {
547
+ if (coll.property.orphanRemoval) {
548
+ const kqb = qb.delete({ [coll.property.mappedBy]: pks })
549
+ .getKnexQuery()
550
+ .whereNotIn(cols, insertDiff);
551
+ return this.rethrow(this.execute(kqb));
552
+ }
553
+ const kqb = qb.update({ [coll.property.mappedBy]: null })
554
+ .getKnexQuery()
555
+ .whereNotIn(cols, insertDiff);
556
+ return this.rethrow(this.execute(kqb));
557
+ }
558
+ const kqb = qb.update({ [coll.property.mappedBy]: pks })
559
+ .getKnexQuery()
560
+ .whereIn(cols, insertDiff);
561
+ return this.rethrow(this.execute(kqb));
562
+ }
454
563
  /* istanbul ignore next */
455
- options ?? (options = {});
456
- options.schema = ownerSchema;
564
+ const pivotMeta = this.metadata.find(coll.property.pivotEntity);
565
+ let schema = pivotMeta.schema;
566
+ if (schema === '*') {
567
+ const ownerSchema = wrapped.getSchema() === '*' ? this.config.get('schema') : wrapped.getSchema();
568
+ schema = coll.property.owner ? ownerSchema : this.config.get('schema');
569
+ }
570
+ else if (schema == null) {
571
+ schema = this.config.get('schema');
572
+ }
573
+ const tableName = `${schema ?? '_'}.${pivotMeta.tableName}`;
574
+ const persister = groups[tableName] ??= new PivotCollectionPersister_1.PivotCollectionPersister(pivotMeta, this, options?.ctx, schema);
575
+ persister.enqueueUpdate(coll.property, insertDiff, deleteDiff, pks);
576
+ }
577
+ for (const persister of core_1.Utils.values(groups)) {
578
+ await this.rethrow(persister.execute());
457
579
  }
458
- return this.rethrow(this.updateCollectionDiff(meta, coll.property, pks, deleteDiff, insertDiff, options));
459
580
  }
460
- async loadFromPivotTable(prop, owners, where = {}, orderBy, ctx, options) {
461
- const pivotProp2 = this.getPivotInverseProperty(prop);
462
- const ownerMeta = this.metadata.find(pivotProp2.type);
581
+ async loadFromPivotTable(prop, owners, where = {}, orderBy, ctx, options, pivotJoin) {
463
582
  const pivotMeta = this.metadata.find(prop.pivotEntity);
464
- const cond = { [`${prop.pivotEntity}.${pivotProp2.name}`]: { $in: ownerMeta.compositePK ? owners : owners.map(o => o[0]) } };
583
+ const pivotProp1 = pivotMeta.relations[prop.owner ? 1 : 0];
584
+ const pivotProp2 = pivotMeta.relations[prop.owner ? 0 : 1];
585
+ const ownerMeta = this.metadata.find(pivotProp2.type);
586
+ options = { ...options };
587
+ const qb = this.createQueryBuilder(prop.pivotEntity, ctx, options.connectionType)
588
+ .withSchema(this.getSchemaName(pivotMeta, options))
589
+ .indexHint(options.indexHint)
590
+ .comment(options.comments)
591
+ .hintComment(options.hintComments);
592
+ const pivotAlias = qb.alias;
593
+ const pivotKey = pivotProp2.joinColumns.map(column => `${pivotAlias}.${column}`).join(core_1.Utils.PK_SEPARATOR);
594
+ const cond = {
595
+ [pivotKey]: { $in: ownerMeta.compositePK ? owners : owners.map(o => o[0]) },
596
+ };
465
597
  /* istanbul ignore if */
466
598
  if (!core_1.Utils.isEmpty(where) && Object.keys(where).every(k => core_1.Utils.isOperator(k, false))) {
467
599
  where = cond;
@@ -469,29 +601,50 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
469
601
  else {
470
602
  where = { ...where, ...cond };
471
603
  }
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;
604
+ orderBy = this.getPivotOrderBy(prop, pivotProp1, pivotAlias, orderBy);
605
+ const populate = this.autoJoinOneToOneOwner(prop.targetMeta, []);
606
+ const fields = [];
607
+ const k1 = !prop.owner ? 'joinColumns' : 'inverseJoinColumns';
608
+ const k2 = prop.owner ? 'joinColumns' : 'inverseJoinColumns';
609
+ const cols = [
610
+ ...prop[k1].map(col => `${pivotAlias}.${col} as fk__${col}`),
611
+ ...prop[k2].map(col => `${pivotAlias}.${col} as fk__${col}`),
612
+ ];
613
+ fields.push(...cols);
614
+ if (!pivotJoin) {
615
+ const targetAlias = qb.getNextAlias(prop.targetMeta.tableName);
616
+ const targetSchema = this.getSchemaName(prop.targetMeta, options) ?? this.platform.getDefaultSchemaName();
617
+ qb.innerJoin(pivotProp1.name, targetAlias, {}, targetSchema);
618
+ const targetFields = this.buildFields(prop.targetMeta, (options.populate ?? []), [], qb, targetAlias, options.fields);
619
+ for (const field of targetFields) {
620
+ const f = field.toString();
621
+ fields.unshift(f.includes('.') ? field : `${targetAlias}.${f}`);
622
+ }
623
+ // we need to handle 1:1 owner auto-joins explicitly, as the QB type is the pivot table, not the target
624
+ populate.forEach(hint => {
625
+ const alias = qb.getNextAlias(prop.targetMeta.tableName);
626
+ qb.leftJoin(`${targetAlias}.${hint.field}`, alias);
627
+ // eslint-disable-next-line dot-notation
628
+ Object.values(qb['_joins']).forEach(join => {
629
+ if (join.alias === alias && join.prop.name === hint.field) {
630
+ fields.push(...qb.helper.mapJoinColumns(qb.type, join));
631
+ }
632
+ });
488
633
  });
489
634
  }
490
- const items = owners.length ? await this.rethrow(qb.execute('all')) : [];
635
+ qb.select(fields)
636
+ .where({ [pivotProp1.name]: where })
637
+ .orderBy(orderBy)
638
+ .setLockMode(options.lockMode, options.lockTableAliases);
639
+ if (owners.length === 1 && (options.offset != null || options.limit != null)) {
640
+ qb.limit(options.limit, options.offset);
641
+ }
642
+ const res = owners.length ? await this.rethrow(qb.execute('all', { mergeResults: false, mapResults: false })) : [];
643
+ const items = res.map((row) => super.mapResult(row, prop.targetMeta));
491
644
  const map = {};
492
645
  const pkProps = ownerMeta.getPrimaryProps();
493
646
  owners.forEach(owner => {
494
- const key = core_1.Utils.getPrimaryKeyHash(prop.joinColumns.map((col, idx) => {
647
+ const key = core_1.Utils.getPrimaryKeyHash(prop.joinColumns.map((_col, idx) => {
495
648
  const pkProp = pkProps[idx];
496
649
  return pkProp.customType ? pkProp.customType.convertToJSValue(owner[idx], this.platform) : owner[idx];
497
650
  }));
@@ -504,10 +657,25 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
504
657
  }));
505
658
  map[key].push(item);
506
659
  prop.joinColumns.forEach(col => delete item[`fk__${col}`]);
507
- prop.inverseJoinColumns.forEach(col => delete item[`fk__${col}`]);
660
+ prop.inverseJoinColumns.forEach((col, idx) => {
661
+ core_1.Utils.renameKey(item, `fk__${col}`, prop.targetMeta.primaryKeys[idx]);
662
+ });
508
663
  });
509
664
  return map;
510
665
  }
666
+ getPivotOrderBy(prop, pivotProp, pivotAlias, orderBy) {
667
+ // FIXME this is ignoring the rest of the array items
668
+ if (!core_1.Utils.isEmpty(orderBy)) {
669
+ return [{ [pivotProp.name]: core_1.Utils.asArray(orderBy)[0] }];
670
+ }
671
+ if (!core_1.Utils.isEmpty(prop.orderBy)) {
672
+ return [{ [pivotProp.name]: core_1.Utils.asArray(prop.orderBy)[0] }];
673
+ }
674
+ if (prop.fixedOrder) {
675
+ return [{ [`${pivotAlias}.${prop.fixedOrderColumn}`]: core_1.QueryOrder.ASC }];
676
+ }
677
+ return [];
678
+ }
511
679
  async execute(queryOrKnex, params = [], method = 'all', ctx) {
512
680
  return this.rethrow(this.connection.execute(queryOrKnex, params, method, ctx));
513
681
  }
@@ -520,54 +688,75 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
520
688
  }
521
689
  const relationsToPopulate = populate.map(({ field }) => field);
522
690
  const toPopulate = meta.relations
523
- .filter(prop => prop.reference === core_1.ReferenceType.ONE_TO_ONE && !prop.owner && !relationsToPopulate.includes(prop.name))
691
+ .filter(prop => prop.kind === core_1.ReferenceKind.ONE_TO_ONE && !prop.owner && !relationsToPopulate.includes(prop.name))
524
692
  .filter(prop => fields.length === 0 || fields.some(f => prop.name === f || prop.name.startsWith(`${String(f)}.`)))
525
693
  .map(prop => ({ field: prop.name, strategy: prop.strategy }));
526
694
  return [...populate, ...toPopulate];
527
695
  }
696
+ /**
697
+ * @internal
698
+ */
528
699
  joinedProps(meta, populate) {
529
700
  return populate.filter(p => {
530
701
  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;
702
+ return (p.strategy || prop.strategy || this.config.get('loadStrategy')) === core_1.LoadStrategy.JOINED && prop.kind !== core_1.ReferenceKind.SCALAR;
532
703
  });
533
704
  }
534
705
  /**
535
706
  * @internal
536
707
  */
537
- mergeJoinedResult(rawResults, meta) {
538
- // group by the root entity primary key first
539
- const map = {};
708
+ mergeJoinedResult(rawResults, meta, joinedProps) {
540
709
  const res = [];
541
- rawResults.forEach(item => {
710
+ const map = {};
711
+ for (const item of rawResults) {
542
712
  const pk = core_1.Utils.getCompositeKeyHash(item, meta);
543
713
  if (map[pk]) {
544
- map[pk].push(item);
714
+ for (const hint of joinedProps) {
715
+ const prop = meta.properties[hint.field];
716
+ if (!item[hint.field]) {
717
+ continue;
718
+ }
719
+ switch (prop.kind) {
720
+ case core_1.ReferenceKind.ONE_TO_MANY:
721
+ case core_1.ReferenceKind.MANY_TO_MANY:
722
+ map[pk][hint.field] = this.mergeJoinedResult([...map[pk][hint.field], ...item[hint.field]], prop.targetMeta, hint.children ?? []);
723
+ break;
724
+ case core_1.ReferenceKind.MANY_TO_ONE:
725
+ case core_1.ReferenceKind.ONE_TO_ONE:
726
+ map[pk][hint.field] = this.mergeJoinedResult([map[pk][hint.field], item[hint.field]], prop.targetMeta, hint.children ?? [])[0];
727
+ break;
728
+ }
729
+ }
545
730
  }
546
731
  else {
547
- map[pk] = [item];
732
+ map[pk] = item;
548
733
  res.push(item);
549
734
  }
550
- });
735
+ }
551
736
  return res;
552
737
  }
553
738
  getFieldsForJoinedLoad(qb, meta, explicitFields, populate = [], parentTableAlias, parentJoinPath) {
554
739
  const fields = [];
555
740
  const joinedProps = this.joinedProps(meta, populate);
741
+ if (explicitFields?.includes('*')) {
742
+ fields.push('*');
743
+ }
556
744
  const shouldHaveColumn = (prop, populate, fields) => {
557
745
  if (!this.platform.shouldHaveColumn(prop, populate)) {
558
746
  return false;
559
747
  }
560
748
  if (!fields || prop.primary) {
561
- return true;
749
+ return !fields?.includes('*');
562
750
  }
563
- return fields.includes(prop.name);
751
+ return fields.some(f => f === prop.name || f.toString().startsWith(prop.name + '.'));
564
752
  };
565
753
  // alias all fields in the primary table
566
754
  meta.props
567
755
  .filter(prop => shouldHaveColumn(prop, populate, explicitFields))
568
756
  .forEach(prop => fields.push(...this.mapPropToFieldNames(qb, prop, parentTableAlias)));
569
757
  joinedProps.forEach(relation => {
570
- const prop = meta.properties[relation.field];
758
+ const [propName] = relation.field.split(':', 2);
759
+ const prop = meta.properties[propName];
571
760
  const meta2 = this.metadata.find(prop.type);
572
761
  const tableAlias = qb.getNextAlias(prop.name);
573
762
  const field = parentTableAlias ? `${parentTableAlias}.${prop.name}` : prop.name;
@@ -587,24 +776,25 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
587
776
  * @internal
588
777
  */
589
778
  mapPropToFieldNames(qb, prop, tableAlias) {
590
- const aliased = qb.ref(tableAlias ? `${tableAlias}__${prop.fieldNames[0]}` : prop.fieldNames[0]).toString();
779
+ const knex = this.connection.getKnex();
780
+ const aliased = knex.ref(tableAlias ? `${tableAlias}__${prop.fieldNames[0]}` : prop.fieldNames[0]).toString();
591
781
  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()];
782
+ const prefixed = knex.ref(prop.fieldNames[0]).withSchema(tableAlias).toString();
783
+ return [(0, core_1.raw)(`${prop.customType.convertToJSValueSQL(prefixed, this.platform)} as ${aliased}`)];
594
784
  }
595
785
  if (prop.formula) {
596
- const alias = qb.ref(tableAlias ?? qb.alias).toString();
597
- return [`${prop.formula(alias)} as ${aliased}`];
786
+ const alias = knex.ref(tableAlias ?? qb.alias).toString();
787
+ return [(0, core_1.raw)(`${prop.formula(alias)} as ${aliased}`)];
598
788
  }
599
789
  if (tableAlias) {
600
- return prop.fieldNames.map(fieldName => qb.ref(fieldName).withSchema(tableAlias).as(`${tableAlias}__${fieldName}`));
790
+ return prop.fieldNames.map(fieldName => knex.ref(fieldName).withSchema(tableAlias).as(`${tableAlias}__${fieldName}`));
601
791
  }
602
792
  return prop.fieldNames;
603
793
  }
604
794
  /** @internal */
605
- createQueryBuilder(entityName, ctx, preferredConnectionType, convertCustomTypes) {
795
+ createQueryBuilder(entityName, ctx, preferredConnectionType, convertCustomTypes, logging) {
606
796
  const connectionType = this.resolveConnectionType({ ctx, connectionType: preferredConnectionType });
607
- const qb = new query_1.QueryBuilder(entityName, this.metadata, this, ctx, undefined, connectionType);
797
+ const qb = new query_1.QueryBuilder(entityName, this.metadata, this, ctx, undefined, connectionType, undefined, logging);
608
798
  if (!convertCustomTypes) {
609
799
  qb.unsetFlag(core_1.QueryFlag.CONVERT_CUSTOM_TYPES);
610
800
  }
@@ -628,7 +818,7 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
628
818
  }
629
819
  const ret = {};
630
820
  this.metadata.find(entityName).relations.forEach(prop => {
631
- if (prop.reference === core_1.ReferenceType.MANY_TO_MANY && data[prop.name]) {
821
+ if (prop.kind === core_1.ReferenceKind.MANY_TO_MANY && data[prop.name]) {
632
822
  ret[prop.name] = data[prop.name].map((item) => core_1.Utils.asArray(item));
633
823
  delete data[prop.name];
634
824
  }
@@ -641,65 +831,31 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
641
831
  }
642
832
  for (const prop of meta.relations) {
643
833
  if (collections[prop.name]) {
644
- await this.rethrow(this.updateCollectionDiff(meta, prop, pks, clear, collections[prop.name], options));
834
+ const pivotMeta = this.metadata.find(prop.pivotEntity);
835
+ const persister = new PivotCollectionPersister_1.PivotCollectionPersister(pivotMeta, this, options?.ctx, options?.schema);
836
+ persister.enqueueUpdate(prop, collections[prop.name], clear, pks);
837
+ await this.rethrow(persister.execute());
645
838
  }
646
839
  }
647
840
  }
648
- async updateCollectionDiff(meta, prop, pks, deleteDiff, insertDiff, options) {
649
- if (!deleteDiff) {
650
- deleteDiff = [];
651
- }
652
- 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);
656
- const knex = qb1.getKnex();
657
- if (Array.isArray(deleteDiff)) {
658
- knex.whereIn(prop.inverseJoinColumns, deleteDiff);
659
- }
660
- prop.joinColumns.forEach((joinColumn, idx) => knex.andWhere(joinColumn, pks[idx]));
661
- await this.execute(knex.delete());
662
- }
663
- if (insertDiff.length === 0) {
664
- return;
665
- }
666
- const items = insertDiff.map(item => {
667
- const cond = {};
668
- prop.joinColumns.forEach((joinColumn, idx) => cond[joinColumn] = pks[idx]);
669
- prop.inverseJoinColumns.forEach((inverseJoinColumn, idx) => cond[inverseJoinColumn] = item[idx]);
670
- return cond;
671
- });
672
- /* istanbul ignore else */
673
- if (this.platform.allowsMultiInsert()) {
674
- await this.nativeInsertMany(prop.pivotEntity, items, { ...options, convertCustomTypes: false, processCollections: false });
675
- }
676
- else {
677
- await core_1.Utils.runSerial(items, item => {
678
- return this.createQueryBuilder(prop.pivotEntity, options?.ctx, 'write')
679
- .unsetFlag(core_1.QueryFlag.CONVERT_CUSTOM_TYPES)
680
- .withSchema(this.getSchemaName(meta, options))
681
- .insert(item)
682
- .execute('run', false);
683
- });
684
- }
685
- }
686
841
  async lockPessimistic(entity, options) {
687
842
  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);
843
+ const qb = this.createQueryBuilder(entity.constructor.name, options.ctx).withSchema(options.schema ?? meta.schema);
689
844
  const cond = core_1.Utils.getPrimaryKeyCond(entity, meta.primaryKeys);
690
- qb.select('1').where(cond).setLockMode(options.lockMode, options.lockTableAliases);
845
+ qb.select((0, core_1.raw)('1')).where(cond).setLockMode(options.lockMode, options.lockTableAliases);
691
846
  await this.rethrow(qb.execute());
692
847
  }
693
848
  buildJoinedPropsOrderBy(entityName, qb, meta, populate, parentPath) {
694
849
  const orderBy = [];
695
850
  const joinedProps = this.joinedProps(meta, populate);
696
851
  joinedProps.forEach(relation => {
697
- const prop = meta.properties[relation.field];
852
+ const [propName] = relation.field.split(':', 2);
853
+ const prop = meta.properties[propName];
698
854
  const propOrderBy = prop.orderBy;
699
855
  const path = `${parentPath ? parentPath : entityName}.${relation.field}`;
700
856
  const propAlias = qb.getAliasForJoinPath(path);
701
857
  if (propOrderBy) {
702
- Object.keys(propOrderBy).forEach(field => {
858
+ core_1.Utils.keys(propOrderBy).forEach(field => {
703
859
  orderBy.push({ [`${propAlias}.${field}`]: propOrderBy[field] });
704
860
  });
705
861
  }
@@ -726,12 +882,12 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
726
882
  return ret;
727
883
  }
728
884
  processField(meta, prop, field, ret, populate, joinedProps, qb) {
729
- if (!prop || (prop.reference === core_1.ReferenceType.ONE_TO_ONE && !prop.owner)) {
885
+ if (!prop || (prop.kind === core_1.ReferenceKind.ONE_TO_ONE && !prop.owner)) {
730
886
  return;
731
887
  }
732
- if (prop.reference === core_1.ReferenceType.EMBEDDED) {
888
+ if (prop.kind === core_1.ReferenceKind.EMBEDDED) {
733
889
  if (prop.object) {
734
- ret.push(prop.fieldNames[0]);
890
+ ret.push(prop.name);
735
891
  return;
736
892
  }
737
893
  const parts = field.split('.');
@@ -743,16 +899,15 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
743
899
  }
744
900
  return;
745
901
  }
746
- if (prop.fieldNames) {
747
- ret.push(prop.fieldNames[0]);
748
- }
902
+ ret.push(prop.name);
749
903
  }
750
- buildFields(meta, populate, joinedProps, qb, fields) {
904
+ buildFields(meta, populate, joinedProps, qb, alias, fields) {
751
905
  const lazyProps = meta.props.filter(prop => prop.lazy && !populate.some(p => p.field === prop.name || p.all));
752
906
  const hasLazyFormulas = meta.props.some(p => p.lazy && p.formula);
753
- const requiresSQLConversion = meta.props.some(p => p.customType?.convertToJSValueSQL);
907
+ const requiresSQLConversion = meta.props.some(p => p.customType?.convertToJSValueSQL && p.persist !== false);
754
908
  const hasExplicitFields = !!fields;
755
909
  const ret = [];
910
+ let addFormulas = false;
756
911
  if (joinedProps.length > 0) {
757
912
  ret.push(...this.getFieldsForJoinedLoad(qb, meta, fields, populate));
758
913
  }
@@ -778,20 +933,22 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
778
933
  else if (lazyProps.filter(p => !p.formula).length > 0) {
779
934
  const props = meta.props.filter(prop => this.platform.shouldHaveColumn(prop, populate, false));
780
935
  ret.push(...core_1.Utils.flatten(props.filter(p => !lazyProps.includes(p)).map(p => p.fieldNames)));
936
+ addFormulas = true;
781
937
  }
782
938
  else if (hasLazyFormulas || requiresSQLConversion) {
783
939
  ret.push('*');
940
+ addFormulas = true;
784
941
  }
785
- if (ret.length > 0 && !hasExplicitFields) {
942
+ if (ret.length > 0 && !hasExplicitFields && addFormulas) {
786
943
  meta.props
787
944
  .filter(prop => prop.formula && !lazyProps.includes(prop))
788
945
  .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}`);
946
+ const a = this.connection.getKnex().ref(alias).toString();
947
+ const aliased = this.connection.getKnex().ref(prop.fieldNames[0]).toString();
948
+ ret.push((0, core_1.raw)(`${prop.formula(a)} as ${aliased}`));
792
949
  });
793
950
  meta.props
794
- .filter(prop => prop.customType?.convertToDatabaseValueSQL || prop.customType?.convertToJSValueSQL)
951
+ .filter(prop => prop.hasConvertToDatabaseValueSQL || prop.hasConvertToJSValueSQL)
795
952
  .forEach(prop => ret.push(prop.name));
796
953
  }
797
954
  return ret.length > 0 ? core_1.Utils.unique(ret) : ['*'];