@mikro-orm/knex 6.0.0-dev.13 → 6.0.0-dev.130

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