@mikro-orm/knex 6.0.0-dev.26 → 6.0.0-dev.261

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/AbstractSqlConnection.d.ts +22 -4
  2. package/AbstractSqlConnection.js +48 -18
  3. package/AbstractSqlDriver.d.ts +32 -24
  4. package/AbstractSqlDriver.js +573 -274
  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 +4 -8
  14. package/SqlEntityRepository.d.ts +7 -6
  15. package/SqlEntityRepository.js +11 -8
  16. package/index.mjs +23 -4
  17. package/package.json +8 -8
  18. package/query/ArrayCriteriaNode.d.ts +5 -4
  19. package/query/ArrayCriteriaNode.js +7 -2
  20. package/query/CriteriaNode.d.ts +12 -10
  21. package/query/CriteriaNode.js +30 -17
  22. package/query/CriteriaNodeFactory.d.ts +6 -6
  23. package/query/CriteriaNodeFactory.js +33 -17
  24. package/query/ObjectCriteriaNode.d.ts +5 -4
  25. package/query/ObjectCriteriaNode.js +77 -33
  26. package/query/QueryBuilder.d.ts +58 -26
  27. package/query/QueryBuilder.js +444 -148
  28. package/query/QueryBuilderHelper.d.ts +13 -18
  29. package/query/QueryBuilderHelper.js +194 -167
  30. package/query/ScalarCriteriaNode.d.ts +4 -3
  31. package/query/ScalarCriteriaNode.js +18 -8
  32. package/query/enums.d.ts +7 -0
  33. package/query/enums.js +10 -2
  34. package/schema/DatabaseSchema.d.ts +5 -2
  35. package/schema/DatabaseSchema.js +28 -10
  36. package/schema/DatabaseTable.d.ts +13 -5
  37. package/schema/DatabaseTable.js +385 -60
  38. package/schema/SchemaComparator.d.ts +7 -5
  39. package/schema/SchemaComparator.js +68 -35
  40. package/schema/SchemaHelper.d.ts +7 -5
  41. package/schema/SchemaHelper.js +25 -11
  42. package/schema/SqlSchemaGenerator.d.ts +3 -4
  43. package/schema/SqlSchemaGenerator.js +71 -28
  44. package/typings.d.ts +30 -11
@@ -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;
@@ -25,21 +29,26 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
25
29
  return this.findVirtual(entityName, where, options);
26
30
  }
27
31
  const populate = this.autoJoinOneToOneOwner(meta, options.populate, options.fields);
28
- 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);
31
- const joinedPropsOrderBy = this.buildJoinedPropsOrderBy(entityName, qb, meta, joinedProps);
32
- const orderBy = [...core_1.Utils.asArray(options.orderBy), ...joinedPropsOrderBy];
32
+ const joinedProps = this.joinedProps(meta, populate, options);
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, options);
35
+ const orderBy = this.buildOrderBy(qb, meta, populate, options);
36
+ core_1.Utils.asArray(options.flags).forEach(flag => qb.setFlag(flag));
33
37
  if (core_1.Utils.isPrimaryKey(where, meta.compositePK)) {
34
38
  where = { [core_1.Utils.getPrimaryKeyHash(meta.primaryKeys)]: where };
35
39
  }
36
40
  const { first, last, before, after } = options;
37
41
  const isCursorPagination = [first, last, before, after].some(v => v != null);
42
+ qb.__populateWhere = options._populateWhere;
38
43
  qb.select(fields)
44
+ // only add populateWhere if we are populate-joining, as this will be used to add `on` conditions
39
45
  .populate(populate, joinedProps.length > 0 ? options.populateWhere : undefined)
40
46
  .where(where)
41
47
  .groupBy(options.groupBy)
42
48
  .having(options.having)
49
+ .indexHint(options.indexHint)
50
+ .comment(options.comments)
51
+ .hintComment(options.hintComments)
43
52
  .withSchema(this.getSchemaName(meta, options));
44
53
  if (isCursorPagination) {
45
54
  const { orderBy: newOrderBy, where } = this.processCursorOptions(meta, options, orderBy);
@@ -48,83 +57,92 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
48
57
  else {
49
58
  qb.orderBy(orderBy);
50
59
  }
51
- if (options.limit !== undefined) {
60
+ if (options.limit != null || options.offset != null) {
52
61
  qb.limit(options.limit, options.offset);
53
62
  }
54
63
  if (options.lockMode) {
55
64
  qb.setLockMode(options.lockMode, options.lockTableAliases);
56
65
  }
57
- core_1.Utils.asArray(options.flags).forEach(flag => qb.setFlag(flag));
58
66
  const result = await this.rethrow(qb.execute('all'));
59
67
  if (isCursorPagination && !first && !!last) {
60
68
  result.reverse();
61
69
  }
62
- if (joinedProps.length > 0) {
63
- return this.mergeJoinedResult(result, meta);
64
- }
65
70
  return result;
66
71
  }
67
72
  async findOne(entityName, where, options) {
68
73
  const opts = { populate: [], ...(options || {}) };
69
74
  const meta = this.metadata.find(entityName);
70
75
  const populate = this.autoJoinOneToOneOwner(meta, opts.populate, opts.fields);
71
- const joinedProps = this.joinedProps(meta, populate);
72
- if (joinedProps.length === 0) {
76
+ const joinedProps = this.joinedProps(meta, populate, options);
77
+ const hasToManyJoins = joinedProps.some(hint => this.hasToManyJoins(hint, meta));
78
+ if (joinedProps.length === 0 || !hasToManyJoins) {
73
79
  opts.limit = 1;
74
80
  }
75
81
  if (opts.limit > 0 && !opts.flags?.includes(core_1.QueryFlag.DISABLE_PAGINATE)) {
76
- opts.flags ?? (opts.flags = []);
82
+ opts.flags ??= [];
77
83
  opts.flags.push(core_1.QueryFlag.DISABLE_PAGINATE);
78
84
  }
79
85
  const res = await this.find(entityName, where, opts);
80
86
  return res[0] || null;
81
87
  }
82
- async findVirtual(entityName, where, options) {
83
- const meta = this.metadata.get(entityName);
84
- /* istanbul ignore next */
85
- if (!meta.expression) {
86
- return [];
88
+ hasToManyJoins(hint, meta) {
89
+ const [propName] = hint.field.split(':', 2);
90
+ const prop = meta.properties[propName];
91
+ if (prop && [core_1.ReferenceKind.ONE_TO_MANY, core_1.ReferenceKind.MANY_TO_MANY].includes(prop.kind)) {
92
+ return true;
87
93
  }
88
- if (typeof meta.expression === 'string') {
89
- return this.wrapVirtualExpressionInSubquery(meta, meta.expression, where, options);
94
+ if (hint.children && prop.targetMeta) {
95
+ return hint.children.some(hint => this.hasToManyJoins(hint, prop.targetMeta));
90
96
  }
91
- const em = this.createEntityManager(false);
92
- em.setTransactionContext(options.ctx);
93
- const res = meta.expression(em, where, options);
94
- if (res instanceof (query_1.QueryBuilder)) {
95
- return this.wrapVirtualExpressionInSubquery(meta, res.getFormattedQuery(), where, options);
96
- }
97
- /* istanbul ignore next */
98
- return res;
97
+ return false;
98
+ }
99
+ async findVirtual(entityName, where, options) {
100
+ return this.findFromVirtual(entityName, where, options, query_1.QueryType.SELECT);
99
101
  }
100
102
  async countVirtual(entityName, where, options) {
103
+ return this.findFromVirtual(entityName, where, options, query_1.QueryType.COUNT);
104
+ }
105
+ async findFromVirtual(entityName, where, options, type) {
101
106
  const meta = this.metadata.get(entityName);
102
107
  /* istanbul ignore next */
103
108
  if (!meta.expression) {
104
- return 0;
109
+ return type === query_1.QueryType.SELECT ? [] : 0;
105
110
  }
106
111
  if (typeof meta.expression === 'string') {
107
- return this.wrapVirtualExpressionInSubquery(meta, meta.expression, where, options, query_1.QueryType.COUNT);
112
+ return this.wrapVirtualExpressionInSubquery(meta, meta.expression, where, options, type);
108
113
  }
109
- const em = this.createEntityManager(false);
114
+ const em = this.createEntityManager();
110
115
  em.setTransactionContext(options.ctx);
111
116
  const res = meta.expression(em, where, options);
112
- if (res instanceof (query_1.QueryBuilder)) {
113
- return this.wrapVirtualExpressionInSubquery(meta, res.getFormattedQuery(), where, options, query_1.QueryType.COUNT);
117
+ if (typeof res === 'string') {
118
+ return this.wrapVirtualExpressionInSubquery(meta, res, where, options, type);
119
+ }
120
+ if (res instanceof query_1.QueryBuilder) {
121
+ return this.wrapVirtualExpressionInSubquery(meta, res.getFormattedQuery(), where, options, type);
122
+ }
123
+ if (core_1.Utils.isObject(res)) {
124
+ const { sql, bindings } = res.toSQL();
125
+ const query = this.platform.formatQuery(sql, bindings);
126
+ return this.wrapVirtualExpressionInSubquery(meta, query, where, options, type);
114
127
  }
115
128
  /* istanbul ignore next */
116
129
  return res;
117
130
  }
118
- async wrapVirtualExpressionInSubquery(meta, expression, where, options, type = query_1.QueryType.SELECT) {
131
+ async wrapVirtualExpressionInSubquery(meta, expression, where, options, type) {
119
132
  const qb = this.createQueryBuilder(meta.className, options?.ctx, options.connectionType, options.convertCustomTypes)
120
- .limit(options?.limit, options?.offset);
121
- if (options.orderBy) {
122
- qb.orderBy(options.orderBy);
133
+ .indexHint(options.indexHint)
134
+ .comment(options.comments)
135
+ .hintComment(options.hintComments);
136
+ if (type !== query_1.QueryType.COUNT) {
137
+ qb.limit(options?.limit, options?.offset);
138
+ if (options.orderBy) {
139
+ qb.orderBy(options.orderBy);
140
+ }
123
141
  }
124
142
  qb.where(where);
125
143
  const kqb = qb.getKnexQuery().clear('select');
126
144
  if (type === query_1.QueryType.COUNT) {
127
- kqb.select(qb.raw('count(*) as count'));
145
+ kqb.select(this.connection.getKnex().raw('count(*) as count'));
128
146
  }
129
147
  else { // select
130
148
  kqb.select('*');
@@ -143,40 +161,88 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
143
161
  return null;
144
162
  }
145
163
  if (qb) {
164
+ // here we map the aliased results (cartesian product) to an object graph
146
165
  this.mapJoinedProps(ret, meta, populate, qb, ret, map);
147
166
  }
148
167
  return ret;
149
168
  }
150
169
  mapJoinedProps(result, meta, populate, qb, root, map, parentJoinPath) {
151
170
  const joinedProps = this.joinedProps(meta, populate);
152
- joinedProps.forEach(p => {
153
- const relation = meta.properties[p.field];
171
+ joinedProps.forEach(hint => {
172
+ const [propName, ref] = hint.field.split(':', 2);
173
+ const prop = meta.properties[propName];
154
174
  /* istanbul ignore next */
155
- if (!relation) {
175
+ if (!prop) {
176
+ return;
177
+ }
178
+ const pivotRefJoin = prop.kind === core_1.ReferenceKind.MANY_TO_MANY && ref;
179
+ const meta2 = this.metadata.find(prop.type);
180
+ let path = parentJoinPath ? `${parentJoinPath}.${prop.name}` : `${meta.name}.${prop.name}`;
181
+ if (!parentJoinPath) {
182
+ path = '[populate]' + path;
183
+ }
184
+ if (pivotRefJoin) {
185
+ path += '[pivot]';
186
+ }
187
+ const relationAlias = qb.getAliasForJoinPath(path, { matchPopulateJoins: true });
188
+ // pivot ref joins via joined strategy need to be handled separately here, as they dont join the target entity
189
+ if (pivotRefJoin) {
190
+ let item;
191
+ if (prop.inverseJoinColumns.length > 1) { // composite keys
192
+ item = prop.inverseJoinColumns.map(name => root[`${relationAlias}__${name}`]);
193
+ }
194
+ else {
195
+ const alias = `${relationAlias}__${prop.inverseJoinColumns[0]}`;
196
+ item = root[alias];
197
+ }
198
+ prop.joinColumns.forEach(name => delete root[`${relationAlias}__${name}`]);
199
+ prop.inverseJoinColumns.forEach(name => delete root[`${relationAlias}__${name}`]);
200
+ result[prop.name] ??= [];
201
+ if (item) {
202
+ result[prop.name].push(item);
203
+ }
156
204
  return;
157
205
  }
158
- const meta2 = this.metadata.find(relation.type);
159
- const path = parentJoinPath ? `${parentJoinPath}.${relation.name}` : `${meta.name}.${relation.name}`;
160
- const relationAlias = qb.getAliasForJoinPath(path);
161
- const relationPojo = {};
162
206
  // If the primary key value for the relation is null, we know we haven't joined to anything
163
207
  // and therefore we don't return any record (since all values would be null)
164
208
  const hasPK = meta2.primaryKeys.every(pk => meta2.properties[pk].fieldNames.every(name => {
165
209
  return root[`${relationAlias}__${name}`] != null;
166
210
  }));
167
211
  if (!hasPK) {
168
- // initialize empty collections
169
- if ([core_1.ReferenceType.MANY_TO_MANY, core_1.ReferenceType.ONE_TO_MANY].includes(relation.reference)) {
170
- result[relation.name] = result[relation.name] || [];
212
+ if ([core_1.ReferenceKind.MANY_TO_MANY, core_1.ReferenceKind.ONE_TO_MANY].includes(prop.kind)) {
213
+ result[prop.name] ??= [];
214
+ }
215
+ if ([core_1.ReferenceKind.MANY_TO_ONE, core_1.ReferenceKind.ONE_TO_ONE].includes(prop.kind)) {
216
+ result[prop.name] ??= null;
171
217
  }
172
218
  return;
173
219
  }
220
+ const relationPojo = {};
174
221
  meta2.props
175
- .filter(prop => this.platform.shouldHaveColumn(prop, p.children || []))
222
+ .filter(prop => prop.persist === false && prop.fieldNames)
223
+ .filter(prop => !prop.lazy || populate.some(p => p.field === prop.name || p.all))
176
224
  .forEach(prop => {
225
+ /* istanbul ignore if */
177
226
  if (prop.fieldNames.length > 1) { // composite keys
178
227
  relationPojo[prop.name] = prop.fieldNames.map(name => root[`${relationAlias}__${name}`]);
228
+ }
229
+ else {
230
+ const alias = `${relationAlias}__${prop.fieldNames[0]}`;
231
+ relationPojo[prop.name] = root[alias];
232
+ }
233
+ });
234
+ meta2.props
235
+ .filter(prop => this.platform.shouldHaveColumn(prop, hint.children || []))
236
+ .forEach(prop => {
237
+ if (prop.fieldNames.length > 1) { // composite keys
238
+ const fk = prop.fieldNames.map(name => root[`${relationAlias}__${name}`]);
179
239
  prop.fieldNames.map(name => delete root[`${relationAlias}__${name}`]);
240
+ relationPojo[prop.name] = core_1.Utils.mapFlatCompositePrimaryKey(fk, prop);
241
+ }
242
+ else if (prop.runtimeType === 'Date') {
243
+ const alias = `${relationAlias}__${prop.fieldNames[0]}`;
244
+ relationPojo[prop.name] = (typeof root[alias] === 'string' ? new Date(root[alias]) : root[alias]);
245
+ delete root[alias];
180
246
  }
181
247
  else {
182
248
  const alias = `${relationAlias}__${prop.fieldNames[0]}`;
@@ -184,41 +250,26 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
184
250
  delete root[alias];
185
251
  }
186
252
  });
187
- const key = `${meta.name}-${(core_1.Utils.getCompositeKeyHash(result, meta))}`;
188
- if (map[key]) {
189
- result[relation.name] = map[key][relation.name];
190
- }
191
- else {
192
- map[key] = result;
193
- }
194
- if ([core_1.ReferenceType.MANY_TO_MANY, core_1.ReferenceType.ONE_TO_MANY].includes(relation.reference)) {
195
- result[relation.name] = result[relation.name] || [];
196
- this.appendToCollection(meta2, result[relation.name], relationPojo);
253
+ if ([core_1.ReferenceKind.MANY_TO_MANY, core_1.ReferenceKind.ONE_TO_MANY].includes(prop.kind)) {
254
+ result[prop.name] ??= [];
255
+ result[prop.name].push(relationPojo);
197
256
  }
198
257
  else {
199
- result[relation.name] = relationPojo;
258
+ result[prop.name] = relationPojo;
200
259
  }
201
- const populateChildren = p.children || [];
260
+ const populateChildren = hint.children || [];
202
261
  this.mapJoinedProps(relationPojo, meta2, populateChildren, qb, root, map, path);
203
262
  });
204
263
  }
205
- appendToCollection(meta, collection, relationPojo) {
206
- if (collection.length === 0) {
207
- return void collection.push(relationPojo);
208
- }
209
- const last = collection[collection.length - 1];
210
- const pk1 = core_1.Utils.getCompositeKeyHash(last, meta);
211
- const pk2 = core_1.Utils.getCompositeKeyHash(relationPojo, meta);
212
- if (pk1 !== pk2) {
213
- collection.push(relationPojo);
214
- }
215
- }
216
264
  async count(entityName, where, options = {}) {
217
265
  const meta = this.metadata.find(entityName);
218
266
  if (meta?.virtual) {
219
267
  return this.countVirtual(entityName, where, options);
220
268
  }
221
269
  const qb = this.createQueryBuilder(entityName, options.ctx, options.connectionType, false)
270
+ .indexHint(options.indexHint)
271
+ .comment(options.comments)
272
+ .hintComment(options.hintComments)
222
273
  .groupBy(options.groupBy)
223
274
  .having(options.having)
224
275
  .populate(options.populate ?? [])
@@ -227,7 +278,7 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
227
278
  return this.rethrow(qb.getCount());
228
279
  }
229
280
  async nativeInsert(entityName, data, options = {}) {
230
- options.convertCustomTypes ?? (options.convertCustomTypes = true);
281
+ options.convertCustomTypes ??= true;
231
282
  const meta = this.metadata.find(entityName);
232
283
  const collections = this.extractManyToMany(entityName, data);
233
284
  const pks = meta?.primaryKeys ?? [this.config.getNamingStrategy().referenceColumnName()];
@@ -247,16 +298,20 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
247
298
  return res;
248
299
  }
249
300
  async nativeInsertMany(entityName, data, options = {}) {
250
- options.processCollections ?? (options.processCollections = true);
251
- options.convertCustomTypes ?? (options.convertCustomTypes = true);
252
- const meta = this.metadata.find(entityName);
301
+ options.processCollections ??= true;
302
+ options.convertCustomTypes ??= true;
303
+ const meta = this.metadata.find(entityName)?.root;
253
304
  const collections = options.processCollections ? data.map(d => this.extractManyToMany(entityName, d)) : [];
254
305
  const pks = this.getPrimaryKeyFields(entityName);
255
306
  const set = new Set();
256
- data.forEach(row => Object.keys(row).forEach(k => set.add(k)));
307
+ data.forEach(row => core_1.Utils.keys(row).forEach(k => set.add(k)));
257
308
  const props = [...set].map(name => meta?.properties[name] ?? { name, fieldNames: [name] });
258
- const fields = core_1.Utils.flatten(props.map(prop => prop.fieldNames));
309
+ let fields = core_1.Utils.flatten(props.map(prop => prop.fieldNames));
310
+ const duplicates = core_1.Utils.findDuplicates(fields);
259
311
  const params = [];
312
+ if (duplicates.length) {
313
+ fields = core_1.Utils.unique(fields);
314
+ }
260
315
  /* istanbul ignore next */
261
316
  const tableName = meta ? this.getTableName(meta, options) : this.platform.quoteIdentifier(entityName);
262
317
  let sql = `insert into ${tableName} `;
@@ -267,29 +322,62 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
267
322
  else {
268
323
  sql += ' ' + data.map(() => `select null as ${this.platform.quoteIdentifier(pks[0])}`).join(' union all ');
269
324
  }
325
+ const addParams = (prop, row) => {
326
+ let value = row[prop.name];
327
+ if (prop.kind === core_1.ReferenceKind.EMBEDDED && prop.object) {
328
+ if (prop.array) {
329
+ for (let i = 0; i < value.length; i++) {
330
+ const item = value[i];
331
+ value[i] = this.mapDataToFieldNames(item, false, prop.embeddedProps, options.convertCustomTypes);
332
+ }
333
+ }
334
+ else {
335
+ value = this.mapDataToFieldNames(value, false, prop.embeddedProps, options.convertCustomTypes);
336
+ }
337
+ }
338
+ if (options.convertCustomTypes && prop.customType) {
339
+ params.push(prop.customType.convertToDatabaseValue(value, this.platform, { key: prop.name, mode: 'query-data' }));
340
+ return;
341
+ }
342
+ params.push(value);
343
+ };
270
344
  if (fields.length > 0 || this.platform.usesDefaultKeyword()) {
271
345
  sql += data.map(row => {
272
346
  const keys = [];
347
+ const usedDups = [];
273
348
  props.forEach(prop => {
274
349
  if (prop.fieldNames.length > 1) {
275
- params.push(...(row[prop.name] ?? prop.fieldNames.map(() => null)));
276
- keys.push(...prop.fieldNames.map(_ => '?'));
277
- }
278
- else if (prop.customType && 'convertToDatabaseValueSQL' in prop.customType && !this.platform.isRaw(row[prop.name])) {
279
- keys.push(prop.customType.convertToDatabaseValueSQL('?', this.platform));
280
- params.push(row[prop.name]);
350
+ const param = core_1.Utils.flatten([...row[prop.name] ?? prop.fieldNames.map(() => null)]);
351
+ const key = param.map(() => '?');
352
+ prop.fieldNames.forEach((field, idx) => {
353
+ if (!duplicates.includes(field) || !usedDups.includes(field)) {
354
+ params.push(param[idx]);
355
+ keys.push(key[idx]);
356
+ usedDups.push(field);
357
+ }
358
+ });
281
359
  }
282
360
  else {
283
- params.push(row[prop.name]);
284
- keys.push('?');
361
+ const field = prop.fieldNames[0];
362
+ if (!duplicates.includes(field) || !usedDups.includes(field)) {
363
+ if (prop.customType && 'convertToDatabaseValueSQL' in prop.customType && !this.platform.isRaw(row[prop.name])) {
364
+ keys.push(prop.customType.convertToDatabaseValueSQL('?', this.platform));
365
+ }
366
+ else {
367
+ keys.push('?');
368
+ }
369
+ addParams(prop, row);
370
+ usedDups.push(field);
371
+ }
285
372
  }
286
373
  });
287
374
  return '(' + (keys.join(', ') || 'default') + ')';
288
375
  }).join(', ');
289
376
  }
290
- if (this.platform.usesReturningStatement()) {
291
- /* istanbul ignore next */
292
- const returningProps = meta.hydrateProps.filter(prop => prop.persist !== false && (prop.primary || prop.defaultRaw));
377
+ if (meta && this.platform.usesReturningStatement()) {
378
+ const returningProps = meta.props
379
+ .filter(prop => prop.persist !== false && prop.defaultRaw || prop.autoincrement || prop.generated)
380
+ .filter(prop => !(prop.name in data[0]) || core_1.Utils.isRawSql(data[0][prop.name]));
293
381
  const returningFields = core_1.Utils.flatten(returningProps.map(prop => prop.fieldNames));
294
382
  /* istanbul ignore next */
295
383
  sql += returningFields.length > 0 ? ` returning ${returningFields.map(field => this.platform.quoteIdentifier(field)).join(', ')}` : '';
@@ -301,8 +389,8 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
301
389
  pk = data.map(d => core_1.Utils.getPrimaryKeyCond(d, pks));
302
390
  }
303
391
  else {
304
- res.row ?? (res.row = {});
305
- res.rows ?? (res.rows = []);
392
+ res.row ??= {};
393
+ res.rows ??= [];
306
394
  pk = data.map((d, i) => d[pks[0]] ?? res.rows[i]?.[pks[0]]).map(d => [d]);
307
395
  res.insertId = res.insertId || res.row[pks[0]];
308
396
  }
@@ -312,7 +400,7 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
312
400
  return res;
313
401
  }
314
402
  async nativeUpdate(entityName, where, data, options = {}) {
315
- options.convertCustomTypes ?? (options.convertCustomTypes = true);
403
+ options.convertCustomTypes ??= true;
316
404
  const meta = this.metadata.find(entityName);
317
405
  const pks = this.getPrimaryKeyFields(entityName);
318
406
  const collections = this.extractManyToMany(entityName, data);
@@ -326,14 +414,27 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
326
414
  .withSchema(this.getSchemaName(meta, options));
327
415
  if (options.upsert) {
328
416
  /* istanbul ignore next */
329
- const uniqueFields = core_1.Utils.isPlainObject(where) ? Object.keys(where) : meta.primaryKeys;
330
- /* istanbul ignore next */
417
+ const uniqueFields = options.onConflictFields ?? (core_1.Utils.isPlainObject(where) ? core_1.Utils.keys(where) : meta.primaryKeys);
418
+ const returning = (0, core_1.getOnConflictReturningFields)(meta, data, uniqueFields, options);
331
419
  qb.insert(data)
332
- .onConflict(uniqueFields.map(p => meta?.properties[p]?.fieldNames[0] ?? p))
333
- .merge(Object.keys(data).filter(f => !uniqueFields.includes(f)));
420
+ .onConflict(uniqueFields)
421
+ .returning(returning);
422
+ if (!options.onConflictAction || options.onConflictAction === 'merge') {
423
+ const fields = (0, core_1.getOnConflictFields)(data, uniqueFields, options);
424
+ qb.merge(fields);
425
+ }
426
+ if (options.onConflictAction === 'ignore') {
427
+ qb.ignore();
428
+ }
334
429
  }
335
430
  else {
336
431
  qb.update(data).where(where);
432
+ // reload generated columns and version fields
433
+ const returning = [];
434
+ meta?.props
435
+ .filter(prop => (prop.generated && !prop.primary) || prop.version)
436
+ .forEach(prop => returning.push(prop.name));
437
+ qb.returning(returning);
337
438
  }
338
439
  res = await this.rethrow(qb.execute('run', false));
339
440
  }
@@ -343,26 +444,65 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
343
444
  return res;
344
445
  }
345
446
  async nativeUpdateMany(entityName, where, data, options = {}) {
346
- options.processCollections ?? (options.processCollections = true);
347
- options.convertCustomTypes ?? (options.convertCustomTypes = true);
447
+ options.processCollections ??= true;
448
+ options.convertCustomTypes ??= true;
348
449
  const meta = this.metadata.get(entityName);
349
450
  if (options.upsert) {
350
- const uniqueFields = core_1.Utils.isPlainObject(where[0]) ? Object.keys(where[0]) : meta.primaryKeys;
451
+ const uniqueFields = options.onConflictFields ?? (core_1.Utils.isPlainObject(where[0]) ? Object.keys(where[0]).flatMap(key => core_1.Utils.splitPrimaryKeys(key)) : meta.primaryKeys);
351
452
  const qb = this.createQueryBuilder(entityName, options.ctx, 'write', options.convertCustomTypes).withSchema(this.getSchemaName(meta, options));
453
+ const returning = (0, core_1.getOnConflictReturningFields)(meta, data[0], uniqueFields, options);
352
454
  qb.insert(data)
353
- .onConflict(uniqueFields.map(p => meta?.properties[p]?.fieldNames[0] ?? p))
354
- .merge(Object.keys(data[0]).filter(f => !uniqueFields.includes(f)));
355
- return qb.execute('run', false);
455
+ .onConflict(uniqueFields)
456
+ .returning(returning);
457
+ if (!options.onConflictAction || options.onConflictAction === 'merge') {
458
+ const fields = (0, core_1.getOnConflictFields)(data[0], uniqueFields, options);
459
+ qb.merge(fields);
460
+ }
461
+ if (options.onConflictAction === 'ignore') {
462
+ qb.ignore();
463
+ }
464
+ return this.rethrow(qb.execute('run', false));
356
465
  }
357
466
  const collections = options.processCollections ? data.map(d => this.extractManyToMany(entityName, d)) : [];
358
467
  const keys = new Set();
359
- data.forEach(row => Object.keys(row).forEach(k => keys.add(k)));
468
+ const fields = new Set();
469
+ const returning = new Set();
470
+ data.forEach(row => {
471
+ core_1.Utils.keys(row).forEach(k => {
472
+ keys.add(k);
473
+ if (core_1.Utils.isRawSql(row[k])) {
474
+ returning.add(k);
475
+ }
476
+ });
477
+ });
478
+ // reload generated columns and version fields
479
+ meta?.props
480
+ .filter(prop => (prop.generated && !prop.primary) || prop.version)
481
+ .forEach(prop => returning.add(prop.name));
360
482
  const pkCond = core_1.Utils.flatten(meta.primaryKeys.map(pk => meta.properties[pk].fieldNames)).map(pk => `${this.platform.quoteIdentifier(pk)} = ?`).join(' and ');
361
483
  const params = [];
362
484
  let sql = `update ${this.getTableName(meta, options)} set `;
485
+ const addParams = (prop, value) => {
486
+ if (prop.kind === core_1.ReferenceKind.EMBEDDED && prop.object) {
487
+ if (prop.array) {
488
+ for (let i = 0; i < value.length; i++) {
489
+ const item = value[i];
490
+ value[i] = this.mapDataToFieldNames(item, false, prop.embeddedProps, options.convertCustomTypes);
491
+ }
492
+ }
493
+ else {
494
+ value = this.mapDataToFieldNames(value, false, prop.embeddedProps, options.convertCustomTypes);
495
+ }
496
+ }
497
+ params.push(value);
498
+ };
363
499
  keys.forEach(key => {
364
500
  const prop = meta.properties[key];
365
501
  prop.fieldNames.forEach((fieldName, fieldNameIdx) => {
502
+ if (fields.has(fieldName)) {
503
+ return;
504
+ }
505
+ fields.add(fieldName);
366
506
  sql += `${this.platform.quoteIdentifier(fieldName)} = case`;
367
507
  where.forEach((cond, idx) => {
368
508
  if (key in data[idx]) {
@@ -374,7 +514,8 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
374
514
  else {
375
515
  sql += '?';
376
516
  }
377
- params.push(...pks, prop.fieldNames.length > 1 ? data[idx][key]?.[fieldNameIdx] : data[idx][key]);
517
+ params.push(...pks);
518
+ addParams(prop, prop.fieldNames.length > 1 ? data[idx][key]?.[fieldNameIdx] : data[idx][key]);
378
519
  }
379
520
  });
380
521
  sql += ` else ${this.platform.quoteIdentifier(fieldName)} end, `;
@@ -385,7 +526,7 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
385
526
  const versionProperty = meta.properties[meta.versionProperty];
386
527
  const quotedFieldName = this.platform.quoteIdentifier(versionProperty.fieldNames[0]);
387
528
  sql += `${quotedFieldName} = `;
388
- if (versionProperty.type.toLowerCase() === 'date') {
529
+ if (versionProperty.runtimeType === 'Date') {
389
530
  sql += this.platform.getCurrentTimestampSQL(versionProperty.length);
390
531
  }
391
532
  else {
@@ -416,6 +557,11 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
416
557
  return '?';
417
558
  });
418
559
  sql += ` in (${conds.join(', ')})`;
560
+ if (this.platform.usesReturningStatement() && returning.size > 0) {
561
+ const returningFields = core_1.Utils.flatten([...returning].map(prop => meta.properties[prop].fieldNames));
562
+ /* istanbul ignore next */
563
+ sql += returningFields.length > 0 ? ` returning ${returningFields.map(field => this.platform.quoteIdentifier(field)).join(', ')}` : '';
564
+ }
419
565
  const res = await this.rethrow(this.execute(sql, params, 'run', options.ctx));
420
566
  for (let i = 0; i < collections.length; i++) {
421
567
  await this.processManyToMany(meta, where[i], collections[i], false, options);
@@ -431,49 +577,81 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
431
577
  const qb = this.createQueryBuilder(entityName, options.ctx, 'write', false).delete(where).withSchema(this.getSchemaName(meta, options));
432
578
  return this.rethrow(qb.execute('run', false));
433
579
  }
434
- async syncCollection(coll, options) {
435
- const wrapped = (0, core_1.helper)(coll.owner);
436
- const meta = wrapped.__meta;
437
- const pks = wrapped.getPrimaryKeys(true);
438
- const snap = coll.getSnapshot();
439
- const includes = (arr, item) => !!arr.find(i => core_1.Utils.equals(i, item));
440
- const snapshot = snap ? snap.map(item => (0, core_1.helper)(item).getPrimaryKeys(true)) : [];
441
- const current = coll.getItems(false).map(item => (0, core_1.helper)(item).getPrimaryKeys(true));
442
- const deleteDiff = snap ? snapshot.filter(item => !includes(current, item)) : true;
443
- const insertDiff = current.filter(item => !includes(snapshot, item));
444
- const target = snapshot.filter(item => includes(current, item)).concat(...insertDiff);
445
- const equals = core_1.Utils.equals(current, target);
446
- const ctx = options?.ctx;
447
- // wrong order if we just delete and insert to the end (only owning sides can have fixed order)
448
- if (coll.property.owner && coll.property.fixedOrder && !equals && Array.isArray(deleteDiff)) {
449
- deleteDiff.length = insertDiff.length = 0;
450
- deleteDiff.push(...snapshot);
451
- insertDiff.push(...current);
452
- }
453
- if (coll.property.reference === core_1.ReferenceType.ONE_TO_MANY) {
454
- const cols = coll.property.referencedColumnNames;
455
- const qb = this.createQueryBuilder(coll.property.type, ctx, 'write')
456
- .withSchema(this.getSchemaName(meta, options))
457
- .update({ [coll.property.mappedBy]: pks })
458
- .getKnexQuery()
459
- .whereIn(cols, insertDiff);
460
- return this.rethrow(this.execute(qb));
461
- }
462
- /* istanbul ignore next */
463
- const ownerSchema = wrapped.getSchema() === '*' ? this.config.get('schema') : wrapped.getSchema();
464
- const pivotMeta = this.metadata.find(coll.property.pivotEntity);
465
- if (pivotMeta.schema === '*') {
580
+ async syncCollections(collections, options) {
581
+ const groups = {};
582
+ for (const coll of collections) {
583
+ const wrapped = (0, core_1.helper)(coll.owner);
584
+ const meta = wrapped.__meta;
585
+ const pks = wrapped.getPrimaryKeys(true);
586
+ const snap = coll.getSnapshot();
587
+ const includes = (arr, item) => !!arr.find(i => core_1.Utils.equals(i, item));
588
+ const snapshot = snap ? snap.map(item => (0, core_1.helper)(item).getPrimaryKeys(true)) : [];
589
+ const current = coll.getItems(false).map(item => (0, core_1.helper)(item).getPrimaryKeys(true));
590
+ const deleteDiff = snap ? snapshot.filter(item => !includes(current, item)) : true;
591
+ const insertDiff = current.filter(item => !includes(snapshot, item));
592
+ const target = snapshot.filter(item => includes(current, item)).concat(...insertDiff);
593
+ const equals = core_1.Utils.equals(current, target);
594
+ // wrong order if we just delete and insert to the end (only owning sides can have fixed order)
595
+ if (coll.property.owner && coll.property.fixedOrder && !equals && Array.isArray(deleteDiff)) {
596
+ deleteDiff.length = insertDiff.length = 0;
597
+ deleteDiff.push(...snapshot);
598
+ insertDiff.push(...current);
599
+ }
600
+ if (coll.property.kind === core_1.ReferenceKind.ONE_TO_MANY) {
601
+ const cols = coll.property.referencedColumnNames;
602
+ const qb = this.createQueryBuilder(coll.property.type, options?.ctx, 'write')
603
+ .withSchema(this.getSchemaName(meta, options));
604
+ if (coll.getSnapshot() === undefined) {
605
+ if (coll.property.orphanRemoval) {
606
+ const kqb = qb.delete({ [coll.property.mappedBy]: pks })
607
+ .getKnexQuery()
608
+ .whereNotIn(cols, insertDiff);
609
+ return this.rethrow(this.execute(kqb));
610
+ }
611
+ const kqb = qb.update({ [coll.property.mappedBy]: null })
612
+ .getKnexQuery()
613
+ .whereNotIn(cols, insertDiff);
614
+ return this.rethrow(this.execute(kqb));
615
+ }
616
+ const kqb = qb.update({ [coll.property.mappedBy]: pks })
617
+ .getKnexQuery()
618
+ .whereIn(cols, insertDiff);
619
+ return this.rethrow(this.execute(kqb));
620
+ }
466
621
  /* istanbul ignore next */
467
- options ?? (options = {});
468
- options.schema = ownerSchema;
622
+ const pivotMeta = this.metadata.find(coll.property.pivotEntity);
623
+ let schema = pivotMeta.schema;
624
+ if (schema === '*') {
625
+ const ownerSchema = wrapped.getSchema() === '*' ? this.config.get('schema') : wrapped.getSchema();
626
+ schema = coll.property.owner ? ownerSchema : this.config.get('schema');
627
+ }
628
+ else if (schema == null) {
629
+ schema = this.config.get('schema');
630
+ }
631
+ const tableName = `${schema ?? '_'}.${pivotMeta.tableName}`;
632
+ const persister = groups[tableName] ??= new PivotCollectionPersister_1.PivotCollectionPersister(pivotMeta, this, options?.ctx, schema);
633
+ persister.enqueueUpdate(coll.property, insertDiff, deleteDiff, pks);
634
+ }
635
+ for (const persister of core_1.Utils.values(groups)) {
636
+ await this.rethrow(persister.execute());
469
637
  }
470
- return this.rethrow(this.updateCollectionDiff(meta, coll.property, pks, deleteDiff, insertDiff, options));
471
638
  }
472
- async loadFromPivotTable(prop, owners, where = {}, orderBy, ctx, options) {
473
- const pivotProp2 = this.getPivotInverseProperty(prop);
474
- const ownerMeta = this.metadata.find(pivotProp2.type);
639
+ async loadFromPivotTable(prop, owners, where = {}, orderBy, ctx, options, pivotJoin) {
475
640
  const pivotMeta = this.metadata.find(prop.pivotEntity);
476
- const cond = { [`${prop.pivotEntity}.${pivotProp2.name}`]: { $in: ownerMeta.compositePK ? owners : owners.map(o => o[0]) } };
641
+ const pivotProp1 = pivotMeta.relations[prop.owner ? 1 : 0];
642
+ const pivotProp2 = pivotMeta.relations[prop.owner ? 0 : 1];
643
+ const ownerMeta = this.metadata.find(pivotProp2.type);
644
+ options = { ...options };
645
+ const qb = this.createQueryBuilder(prop.pivotEntity, ctx, options.connectionType)
646
+ .withSchema(this.getSchemaName(pivotMeta, options))
647
+ .indexHint(options.indexHint)
648
+ .comment(options.comments)
649
+ .hintComment(options.hintComments);
650
+ const pivotAlias = qb.alias;
651
+ const pivotKey = pivotProp2.joinColumns.map(column => `${pivotAlias}.${column}`).join(core_1.Utils.PK_SEPARATOR);
652
+ const cond = {
653
+ [pivotKey]: { $in: ownerMeta.compositePK ? owners : owners.map(o => o[0]) },
654
+ };
477
655
  /* istanbul ignore if */
478
656
  if (!core_1.Utils.isEmpty(where) && Object.keys(where).every(k => core_1.Utils.isOperator(k, false))) {
479
657
  where = cond;
@@ -481,29 +659,51 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
481
659
  else {
482
660
  where = { ...where, ...cond };
483
661
  }
484
- orderBy = this.getPivotOrderBy(prop, orderBy);
485
- const qb = this.createQueryBuilder(prop.type, ctx, options?.connectionType)
486
- .unsetFlag(core_1.QueryFlag.CONVERT_CUSTOM_TYPES)
487
- .withSchema(this.getSchemaName(prop.targetMeta, options));
488
- const populate = this.autoJoinOneToOneOwner(prop.targetMeta, [{ field: prop.pivotEntity }]);
489
- const fields = this.buildFields(prop.targetMeta, (options?.populate ?? []), [], qb, options?.fields);
490
- qb.select(fields).populate(populate).where(where).orderBy(orderBy).setLockMode(options?.lockMode, options?.lockTableAliases);
491
- if (owners.length === 1 && (options?.offset != null || options?.limit != null)) {
492
- qb.limit(options.limit, options.offset);
493
- }
494
- if (prop.targetMeta.schema !== '*' && pivotMeta.schema === '*' && options?.schema) {
495
- // eslint-disable-next-line dot-notation
496
- qb['finalize']();
497
- // eslint-disable-next-line dot-notation
498
- Object.values(qb['_joins']).forEach(join => {
499
- join.schema = options.schema;
662
+ orderBy = this.getPivotOrderBy(prop, pivotProp1, pivotAlias, orderBy);
663
+ const populate = this.autoJoinOneToOneOwner(prop.targetMeta, []);
664
+ const fields = [];
665
+ const k1 = !prop.owner ? 'joinColumns' : 'inverseJoinColumns';
666
+ const k2 = prop.owner ? 'joinColumns' : 'inverseJoinColumns';
667
+ const cols = [
668
+ ...prop[k1].map(col => `${pivotAlias}.${col} as fk__${col}`),
669
+ ...prop[k2].map(col => `${pivotAlias}.${col} as fk__${col}`),
670
+ ];
671
+ fields.push(...cols);
672
+ if (!pivotJoin) {
673
+ const targetAlias = qb.getNextAlias(prop.targetMeta.tableName);
674
+ const targetSchema = this.getSchemaName(prop.targetMeta, options) ?? this.platform.getDefaultSchemaName();
675
+ qb.innerJoin(pivotProp1.name, targetAlias, {}, targetSchema);
676
+ const targetFields = this.buildFields(prop.targetMeta, (options.populate ?? []), [], qb, targetAlias, options.fields, options);
677
+ for (const field of targetFields) {
678
+ const f = field.toString();
679
+ fields.unshift(f.includes('.') ? field : `${targetAlias}.${f}`);
680
+ }
681
+ // we need to handle 1:1 owner auto-joins explicitly, as the QB type is the pivot table, not the target
682
+ populate.forEach(hint => {
683
+ const alias = qb.getNextAlias(prop.targetMeta.tableName);
684
+ qb.leftJoin(`${targetAlias}.${hint.field}`, alias);
685
+ // eslint-disable-next-line dot-notation
686
+ Object.values(qb['_joins']).forEach(join => {
687
+ const [propName] = hint.field.split(':', 2);
688
+ if (join.alias === alias && join.prop.name === propName) {
689
+ fields.push(...qb.helper.mapJoinColumns(qb.type, join));
690
+ }
691
+ });
500
692
  });
501
693
  }
502
- const items = owners.length ? await this.rethrow(qb.execute('all')) : [];
694
+ qb.select(fields)
695
+ .where({ [pivotProp1.name]: where })
696
+ .orderBy(orderBy)
697
+ .setLockMode(options.lockMode, options.lockTableAliases);
698
+ if (owners.length === 1 && (options.offset != null || options.limit != null)) {
699
+ qb.limit(options.limit, options.offset);
700
+ }
701
+ const res = owners.length ? await this.rethrow(qb.execute('all', { mergeResults: false, mapResults: false })) : [];
702
+ const items = res.map((row) => super.mapResult(row, prop.targetMeta));
503
703
  const map = {};
504
704
  const pkProps = ownerMeta.getPrimaryProps();
505
705
  owners.forEach(owner => {
506
- const key = core_1.Utils.getPrimaryKeyHash(prop.joinColumns.map((col, idx) => {
706
+ const key = core_1.Utils.getPrimaryKeyHash(prop.joinColumns.map((_col, idx) => {
507
707
  const pkProp = pkProps[idx];
508
708
  return pkProp.customType ? pkProp.customType.convertToJSValue(owner[idx], this.platform) : owner[idx];
509
709
  }));
@@ -516,10 +716,25 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
516
716
  }));
517
717
  map[key].push(item);
518
718
  prop.joinColumns.forEach(col => delete item[`fk__${col}`]);
519
- prop.inverseJoinColumns.forEach(col => delete item[`fk__${col}`]);
719
+ prop.inverseJoinColumns.forEach((col, idx) => {
720
+ core_1.Utils.renameKey(item, `fk__${col}`, prop.targetMeta.primaryKeys[idx]);
721
+ });
520
722
  });
521
723
  return map;
522
724
  }
725
+ getPivotOrderBy(prop, pivotProp, pivotAlias, orderBy) {
726
+ // FIXME this is ignoring the rest of the array items
727
+ if (!core_1.Utils.isEmpty(orderBy)) {
728
+ return [{ [pivotProp.name]: core_1.Utils.asArray(orderBy)[0] }];
729
+ }
730
+ if (!core_1.Utils.isEmpty(prop.orderBy)) {
731
+ return [{ [pivotProp.name]: core_1.Utils.asArray(prop.orderBy)[0] }];
732
+ }
733
+ if (prop.fixedOrder) {
734
+ return [{ [`${pivotAlias}.${prop.fixedOrderColumn}`]: core_1.QueryOrder.ASC }];
735
+ }
736
+ return [];
737
+ }
523
738
  async execute(queryOrKnex, params = [], method = 'all', ctx) {
524
739
  return this.rethrow(this.connection.execute(queryOrKnex, params, method, ctx));
525
740
  }
@@ -532,39 +747,69 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
532
747
  }
533
748
  const relationsToPopulate = populate.map(({ field }) => field);
534
749
  const toPopulate = meta.relations
535
- .filter(prop => prop.reference === core_1.ReferenceType.ONE_TO_ONE && !prop.owner && !relationsToPopulate.includes(prop.name))
750
+ .filter(prop => prop.kind === core_1.ReferenceKind.ONE_TO_ONE && !prop.owner && !relationsToPopulate.includes(prop.name))
536
751
  .filter(prop => fields.length === 0 || fields.some(f => prop.name === f || prop.name.startsWith(`${String(f)}.`)))
537
- .map(prop => ({ field: prop.name, strategy: prop.strategy }));
752
+ .map(prop => ({ field: `${prop.name}:ref`, strategy: prop.strategy }));
538
753
  return [...populate, ...toPopulate];
539
754
  }
540
- joinedProps(meta, populate) {
541
- return populate.filter(p => {
542
- const prop = meta.properties[p.field] || {};
543
- return (p.strategy || prop.strategy || this.config.get('loadStrategy')) === core_1.LoadStrategy.JOINED && prop.reference !== core_1.ReferenceType.SCALAR;
755
+ /**
756
+ * @internal
757
+ */
758
+ joinedProps(meta, populate, options) {
759
+ return populate.filter(hint => {
760
+ const [propName, ref] = hint.field.split(':', 2);
761
+ const prop = meta.properties[propName] || {};
762
+ if ((options?.strategy || hint.strategy || prop.strategy || this.config.get('loadStrategy')) !== core_1.LoadStrategy.JOINED) {
763
+ return false;
764
+ }
765
+ if (ref) {
766
+ // keep only pivot ref joins here, as that only triggers actual join
767
+ return prop.kind === core_1.ReferenceKind.MANY_TO_MANY;
768
+ }
769
+ return ![core_1.ReferenceKind.SCALAR, core_1.ReferenceKind.EMBEDDED].includes(prop.kind);
544
770
  });
545
771
  }
546
772
  /**
547
773
  * @internal
548
774
  */
549
- mergeJoinedResult(rawResults, meta) {
550
- // group by the root entity primary key first
551
- const map = {};
775
+ mergeJoinedResult(rawResults, meta, joinedProps) {
552
776
  const res = [];
553
- rawResults.forEach(item => {
777
+ const map = {};
778
+ for (const item of rawResults) {
554
779
  const pk = core_1.Utils.getCompositeKeyHash(item, meta);
555
780
  if (map[pk]) {
556
- map[pk].push(item);
781
+ for (const hint of joinedProps) {
782
+ const [propName, ref] = hint.field.split(':', 2);
783
+ const prop = meta.properties[propName];
784
+ if (!item[propName]) {
785
+ continue;
786
+ }
787
+ if (prop.kind === core_1.ReferenceKind.MANY_TO_MANY && ref) {
788
+ map[pk][propName] = [...map[pk][propName], ...item[propName]];
789
+ continue;
790
+ }
791
+ switch (prop.kind) {
792
+ case core_1.ReferenceKind.ONE_TO_MANY:
793
+ case core_1.ReferenceKind.MANY_TO_MANY:
794
+ map[pk][propName] = this.mergeJoinedResult([...map[pk][propName], ...item[propName]], prop.targetMeta, hint.children ?? []);
795
+ break;
796
+ case core_1.ReferenceKind.MANY_TO_ONE:
797
+ case core_1.ReferenceKind.ONE_TO_ONE:
798
+ map[pk][propName] = this.mergeJoinedResult([map[pk][propName], item[propName]], prop.targetMeta, hint.children ?? [])[0];
799
+ break;
800
+ }
801
+ }
557
802
  }
558
803
  else {
559
- map[pk] = [item];
804
+ map[pk] = item;
560
805
  res.push(item);
561
806
  }
562
- });
807
+ }
563
808
  return res;
564
809
  }
565
- getFieldsForJoinedLoad(qb, meta, explicitFields, populate = [], parentTableAlias, parentJoinPath) {
810
+ getFieldsForJoinedLoad(qb, meta, explicitFields, populate = [], options, parentTableAlias, parentJoinPath) {
566
811
  const fields = [];
567
- const joinedProps = this.joinedProps(meta, populate);
812
+ const joinedProps = this.joinedProps(meta, populate, options);
568
813
  if (explicitFields?.includes('*')) {
569
814
  fields.push('*');
570
815
  }
@@ -575,26 +820,41 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
575
820
  if (!fields || prop.primary) {
576
821
  return !fields?.includes('*');
577
822
  }
578
- return fields.includes(prop.name);
823
+ return fields.some(f => f === prop.name || f.toString().startsWith(prop.name + '.'));
579
824
  };
580
- // alias all fields in the primary table
581
- meta.props
582
- .filter(prop => shouldHaveColumn(prop, populate, explicitFields))
583
- .forEach(prop => fields.push(...this.mapPropToFieldNames(qb, prop, parentTableAlias)));
584
- joinedProps.forEach(relation => {
585
- const prop = meta.properties[relation.field];
825
+ const populateWhereAll = options?._populateWhere === 'all' || core_1.Utils.isEmpty(options?._populateWhere);
826
+ // root entity is already handled, skip that
827
+ if (parentJoinPath) {
828
+ // alias all fields in the primary table
829
+ meta.props
830
+ .filter(prop => shouldHaveColumn(prop, populate, explicitFields))
831
+ .forEach(prop => fields.push(...this.mapPropToFieldNames(qb, prop, parentTableAlias)));
832
+ }
833
+ joinedProps.forEach(hint => {
834
+ const [propName, ref] = hint.field.split(':', 2);
835
+ const prop = meta.properties[propName];
586
836
  const meta2 = this.metadata.find(prop.type);
837
+ const pivotRefJoin = prop.kind === core_1.ReferenceKind.MANY_TO_MANY && ref;
587
838
  const tableAlias = qb.getNextAlias(prop.name);
588
839
  const field = parentTableAlias ? `${parentTableAlias}.${prop.name}` : prop.name;
589
- const path = parentJoinPath ? `${parentJoinPath}.${prop.name}` : `${meta.name}.${prop.name}`;
590
- qb.join(field, tableAlias, {}, 'leftJoin', path);
840
+ let path = parentJoinPath ? `${parentJoinPath}.${prop.name}` : `${meta.name}.${prop.name}`;
841
+ if (!parentJoinPath && populateWhereAll && !path.startsWith('[populate]')) {
842
+ path = '[populate]' + path;
843
+ }
844
+ const joinType = pivotRefJoin ? query_1.JoinType.pivotJoin : query_1.JoinType.leftJoin;
845
+ qb.join(field, tableAlias, {}, joinType, path);
846
+ if (pivotRefJoin) {
847
+ fields.push(...prop.joinColumns.map(col => qb.helper.mapper(`${tableAlias}.${col}`, qb.type, undefined, `${tableAlias}__${col}`)), ...prop.inverseJoinColumns.map(col => qb.helper.mapper(`${tableAlias}.${col}`, qb.type, undefined, `${tableAlias}__${col}`)));
848
+ }
591
849
  const childExplicitFields = explicitFields?.filter(f => core_1.Utils.isPlainObject(f)).map(o => o[prop.name])[0] || [];
592
850
  explicitFields?.forEach(f => {
593
851
  if (typeof f === 'string' && f.startsWith(`${prop.name}.`)) {
594
852
  childExplicitFields.push(f.substring(prop.name.length + 1));
595
853
  }
596
854
  });
597
- fields.push(...this.getFieldsForJoinedLoad(qb, meta2, childExplicitFields.length === 0 ? undefined : childExplicitFields, relation.children, tableAlias, path));
855
+ if (!ref) {
856
+ fields.push(...this.getFieldsForJoinedLoad(qb, meta2, childExplicitFields.length === 0 ? undefined : childExplicitFields, hint.children, options, tableAlias, path));
857
+ }
598
858
  });
599
859
  return fields;
600
860
  }
@@ -602,24 +862,35 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
602
862
  * @internal
603
863
  */
604
864
  mapPropToFieldNames(qb, prop, tableAlias) {
605
- const aliased = qb.ref(tableAlias ? `${tableAlias}__${prop.fieldNames[0]}` : prop.fieldNames[0]).toString();
606
- if (prop.customType?.convertToJSValueSQL && tableAlias) {
607
- const prefixed = qb.ref(prop.fieldNames[0]).withSchema(tableAlias).toString();
608
- return [qb.raw(prop.customType.convertToJSValueSQL(prefixed, this.platform) + ' as ' + aliased).toString()];
865
+ const knex = this.connection.getKnex();
866
+ const aliased = knex.ref(tableAlias ? `${tableAlias}__${prop.fieldNames[0]}` : prop.fieldNames[0]).toString();
867
+ if (tableAlias && prop.customTypes?.some(type => type?.convertToJSValueSQL)) {
868
+ return prop.fieldNames.map((col, idx) => {
869
+ if (!prop.customTypes[idx]?.convertToJSValueSQL) {
870
+ return col;
871
+ }
872
+ const prefixed = knex.ref(col).withSchema(tableAlias).toString();
873
+ const aliased = knex.ref(`${tableAlias}__${col}`).toString();
874
+ return (0, core_1.raw)(`${prop.customTypes[idx].convertToJSValueSQL(prefixed, this.platform)} as ${aliased}`);
875
+ });
876
+ }
877
+ if (tableAlias && prop.customType?.convertToJSValueSQL) {
878
+ const prefixed = knex.ref(prop.fieldNames[0]).withSchema(tableAlias).toString();
879
+ return [(0, core_1.raw)(`${prop.customType.convertToJSValueSQL(prefixed, this.platform)} as ${aliased}`)];
609
880
  }
610
881
  if (prop.formula) {
611
- const alias = qb.ref(tableAlias ?? qb.alias).toString();
612
- return [`${prop.formula(alias)} as ${aliased}`];
882
+ const alias = knex.ref(tableAlias ?? qb.alias).toString();
883
+ return [(0, core_1.raw)(`${prop.formula(alias)} as ${aliased}`)];
613
884
  }
614
885
  if (tableAlias) {
615
- return prop.fieldNames.map(fieldName => qb.ref(fieldName).withSchema(tableAlias).as(`${tableAlias}__${fieldName}`));
886
+ return prop.fieldNames.map(fieldName => knex.ref(fieldName).withSchema(tableAlias).as(`${tableAlias}__${fieldName}`));
616
887
  }
617
888
  return prop.fieldNames;
618
889
  }
619
890
  /** @internal */
620
- createQueryBuilder(entityName, ctx, preferredConnectionType, convertCustomTypes) {
891
+ createQueryBuilder(entityName, ctx, preferredConnectionType, convertCustomTypes, logging) {
621
892
  const connectionType = this.resolveConnectionType({ ctx, connectionType: preferredConnectionType });
622
- const qb = new query_1.QueryBuilder(entityName, this.metadata, this, ctx, undefined, connectionType);
893
+ const qb = new query_1.QueryBuilder(entityName, this.metadata, this, ctx, undefined, connectionType, undefined, logging);
623
894
  if (!convertCustomTypes) {
624
895
  qb.unsetFlag(core_1.QueryFlag.CONVERT_CUSTOM_TYPES);
625
896
  }
@@ -643,7 +914,7 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
643
914
  }
644
915
  const ret = {};
645
916
  this.metadata.find(entityName).relations.forEach(prop => {
646
- if (prop.reference === core_1.ReferenceType.MANY_TO_MANY && data[prop.name]) {
917
+ if (prop.kind === core_1.ReferenceKind.MANY_TO_MANY && data[prop.name]) {
647
918
  ret[prop.name] = data[prop.name].map((item) => core_1.Utils.asArray(item));
648
919
  delete data[prop.name];
649
920
  }
@@ -656,77 +927,97 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
656
927
  }
657
928
  for (const prop of meta.relations) {
658
929
  if (collections[prop.name]) {
659
- await this.rethrow(this.updateCollectionDiff(meta, prop, pks, clear, collections[prop.name], options));
930
+ const pivotMeta = this.metadata.find(prop.pivotEntity);
931
+ const persister = new PivotCollectionPersister_1.PivotCollectionPersister(pivotMeta, this, options?.ctx, options?.schema);
932
+ persister.enqueueUpdate(prop, collections[prop.name], clear, pks);
933
+ await this.rethrow(persister.execute());
660
934
  }
661
935
  }
662
936
  }
663
- async updateCollectionDiff(meta, prop, pks, deleteDiff, insertDiff, options) {
664
- if (!deleteDiff) {
665
- deleteDiff = [];
666
- }
667
- if (deleteDiff === true || deleteDiff.length > 0) {
668
- const qb1 = this.createQueryBuilder(prop.pivotEntity, options?.ctx, 'write')
669
- .withSchema(this.getSchemaName(meta, options))
670
- .unsetFlag(core_1.QueryFlag.CONVERT_CUSTOM_TYPES);
671
- const knex = qb1.getKnex();
672
- if (Array.isArray(deleteDiff)) {
673
- knex.whereIn(prop.inverseJoinColumns, deleteDiff);
674
- }
675
- prop.joinColumns.forEach((joinColumn, idx) => knex.andWhere(joinColumn, pks[idx]));
676
- await this.execute(knex.delete());
677
- }
678
- if (insertDiff.length === 0) {
679
- return;
680
- }
681
- const items = insertDiff.map(item => {
682
- const cond = {};
683
- prop.joinColumns.forEach((joinColumn, idx) => cond[joinColumn] = pks[idx]);
684
- prop.inverseJoinColumns.forEach((inverseJoinColumn, idx) => cond[inverseJoinColumn] = item[idx]);
685
- return cond;
686
- });
687
- /* istanbul ignore else */
688
- if (this.platform.allowsMultiInsert()) {
689
- await this.nativeInsertMany(prop.pivotEntity, items, {
690
- ...options,
691
- convertCustomTypes: false,
692
- processCollections: false,
693
- });
694
- }
695
- else {
696
- await core_1.Utils.runSerial(items, item => {
697
- return this.createQueryBuilder(prop.pivotEntity, options?.ctx, 'write')
698
- .unsetFlag(core_1.QueryFlag.CONVERT_CUSTOM_TYPES)
699
- .withSchema(this.getSchemaName(meta, options))
700
- .insert(item)
701
- .execute('run', false);
702
- });
703
- }
704
- }
705
937
  async lockPessimistic(entity, options) {
706
938
  const meta = (0, core_1.helper)(entity).__meta;
707
- const qb = this.createQueryBuilder(entity.constructor.name, options.ctx).unsetFlag(core_1.QueryFlag.CONVERT_CUSTOM_TYPES).withSchema(options.schema ?? meta.schema);
939
+ const qb = this.createQueryBuilder(entity.constructor.name, options.ctx).withSchema(options.schema ?? meta.schema);
708
940
  const cond = core_1.Utils.getPrimaryKeyCond(entity, meta.primaryKeys);
709
- qb.select('1').where(cond).setLockMode(options.lockMode, options.lockTableAliases);
941
+ qb.select((0, core_1.raw)('1')).where(cond).setLockMode(options.lockMode, options.lockTableAliases);
710
942
  await this.rethrow(qb.execute());
711
943
  }
712
- buildJoinedPropsOrderBy(entityName, qb, meta, populate, parentPath) {
944
+ buildOrderBy(qb, meta, populate, options) {
945
+ const joinedProps = this.joinedProps(meta, populate, options);
946
+ // `options._populateWhere` is a copy of the value provided by user with a fallback to the global config option
947
+ // as `options.populateWhere` will be always recomputed to respect filters
948
+ const populateWhereAll = options._populateWhere !== 'infer' && !core_1.Utils.isEmpty(options._populateWhere);
949
+ const path = (populateWhereAll ? '[populate]' : '') + meta.className;
950
+ const populateOrderBy = this.buildPopulateOrderBy(qb, meta, core_1.Utils.asArray(options.populateOrderBy ?? options.orderBy), path);
951
+ const joinedPropsOrderBy = this.buildJoinedPropsOrderBy(qb, meta, joinedProps, options, path);
952
+ return [...core_1.Utils.asArray(options.orderBy), ...populateOrderBy, ...joinedPropsOrderBy];
953
+ }
954
+ buildPopulateOrderBy(qb, meta, populateOrderBy, parentPath, parentAlias = qb.alias) {
713
955
  const orderBy = [];
714
- const joinedProps = this.joinedProps(meta, populate);
715
- joinedProps.forEach(relation => {
716
- const prop = meta.properties[relation.field];
956
+ for (let i = 0; i < populateOrderBy.length; i++) {
957
+ const orderHint = populateOrderBy[i];
958
+ for (const propName of core_1.Utils.keys(orderHint)) {
959
+ const prop = meta.properties[propName];
960
+ const meta2 = this.metadata.find(prop.type);
961
+ const childOrder = orderHint[prop.name];
962
+ let path = `${parentPath}.${propName}`;
963
+ if (prop.kind === core_1.ReferenceKind.MANY_TO_MANY && typeof childOrder !== 'object') {
964
+ path += '[pivot]';
965
+ }
966
+ const join = qb.getJoinForPath(path, { matchPopulateJoins: true });
967
+ const propAlias = qb.getAliasForJoinPath(join ?? path, { matchPopulateJoins: true }) ?? parentAlias;
968
+ if (!join && parentAlias === qb.alias) {
969
+ continue;
970
+ }
971
+ if (![core_1.ReferenceKind.SCALAR, core_1.ReferenceKind.EMBEDDED].includes(prop.kind) && typeof childOrder === 'object') {
972
+ const children = this.buildPopulateOrderBy(qb, meta2, core_1.Utils.asArray(childOrder), path, propAlias);
973
+ orderBy.push(...children);
974
+ continue;
975
+ }
976
+ if (prop.kind === core_1.ReferenceKind.MANY_TO_MANY && join) {
977
+ if (prop.fixedOrderColumn) {
978
+ orderBy.push({ [`${join.alias}.${prop.fixedOrderColumn}`]: childOrder });
979
+ }
980
+ else {
981
+ for (const col of prop.inverseJoinColumns) {
982
+ orderBy.push({ [`${join.ownerAlias}.${col}`]: childOrder });
983
+ }
984
+ }
985
+ continue;
986
+ }
987
+ const order = typeof childOrder === 'object' ? childOrder[propName] : childOrder;
988
+ orderBy.push({ [`${propAlias}.${propName}`]: order });
989
+ }
990
+ }
991
+ return orderBy;
992
+ }
993
+ buildJoinedPropsOrderBy(qb, meta, populate, options, parentPath) {
994
+ const orderBy = [];
995
+ const joinedProps = this.joinedProps(meta, populate, options);
996
+ for (const hint of joinedProps) {
997
+ const [propName, ref] = hint.field.split(':', 2);
998
+ const prop = meta.properties[propName];
717
999
  const propOrderBy = prop.orderBy;
718
- const path = `${parentPath ? parentPath : entityName}.${relation.field}`;
719
- const propAlias = qb.getAliasForJoinPath(path);
1000
+ let path = `${parentPath}.${propName}`;
1001
+ if (prop.kind === core_1.ReferenceKind.MANY_TO_MANY && ref) {
1002
+ path += '[pivot]';
1003
+ }
1004
+ const join = qb.getJoinForPath(path, { matchPopulateJoins: true });
1005
+ const propAlias = qb.getAliasForJoinPath(join ?? path, { matchPopulateJoins: true });
1006
+ const meta2 = this.metadata.find(prop.type);
1007
+ if (prop.kind === core_1.ReferenceKind.MANY_TO_MANY && prop.fixedOrder && join) {
1008
+ const alias = ref ? propAlias : join.ownerAlias;
1009
+ orderBy.push({ [`${alias}.${prop.fixedOrderColumn}`]: core_1.QueryOrder.ASC });
1010
+ }
720
1011
  if (propOrderBy) {
721
- Object.keys(propOrderBy).forEach(field => {
1012
+ core_1.Utils.keys(propOrderBy).forEach(field => {
722
1013
  orderBy.push({ [`${propAlias}.${field}`]: propOrderBy[field] });
723
1014
  });
724
1015
  }
725
- if (relation.children) {
726
- const meta2 = this.metadata.find(prop.type);
727
- orderBy.push(...this.buildJoinedPropsOrderBy(prop.name, qb, meta2, relation.children, path));
1016
+ if (hint.children) {
1017
+ const buildJoinedPropsOrderBy = this.buildJoinedPropsOrderBy(qb, meta2, hint.children, options, path);
1018
+ orderBy.push(...buildJoinedPropsOrderBy);
728
1019
  }
729
- });
1020
+ }
730
1021
  return orderBy;
731
1022
  }
732
1023
  normalizeFields(fields, prefix = '') {
@@ -745,12 +1036,12 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
745
1036
  return ret;
746
1037
  }
747
1038
  processField(meta, prop, field, ret, populate, joinedProps, qb) {
748
- if (!prop || (prop.reference === core_1.ReferenceType.ONE_TO_ONE && !prop.owner)) {
1039
+ if (!prop || (prop.kind === core_1.ReferenceKind.ONE_TO_ONE && !prop.owner)) {
749
1040
  return;
750
1041
  }
751
- if (prop.reference === core_1.ReferenceType.EMBEDDED) {
1042
+ if (prop.kind === core_1.ReferenceKind.EMBEDDED) {
752
1043
  if (prop.object) {
753
- ret.push(prop.fieldNames[0]);
1044
+ ret.push(prop.name);
754
1045
  return;
755
1046
  }
756
1047
  const parts = field.split('.');
@@ -762,20 +1053,17 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
762
1053
  }
763
1054
  return;
764
1055
  }
765
- if (prop.fieldNames) {
766
- ret.push(...prop.fieldNames);
767
- }
1056
+ ret.push(prop.name);
768
1057
  }
769
- buildFields(meta, populate, joinedProps, qb, fields) {
1058
+ buildFields(meta, populate, joinedProps, qb, alias, fields, options) {
770
1059
  const lazyProps = meta.props.filter(prop => prop.lazy && !populate.some(p => p.field === prop.name || p.all));
771
1060
  const hasLazyFormulas = meta.props.some(p => p.lazy && p.formula);
772
- const requiresSQLConversion = meta.props.some(p => p.customType?.convertToJSValueSQL);
1061
+ const requiresSQLConversion = meta.props.some(p => p.customType?.convertToJSValueSQL && p.persist !== false);
773
1062
  const hasExplicitFields = !!fields;
774
1063
  const ret = [];
775
- if (joinedProps.length > 0) {
776
- ret.push(...this.getFieldsForJoinedLoad(qb, meta, fields, populate));
777
- }
778
- else if (fields) {
1064
+ let addFormulas = false;
1065
+ // handle root entity properties first, this is used for both strategies in the same way
1066
+ if (fields) {
779
1067
  for (const field of this.normalizeFields(fields)) {
780
1068
  if (field === '*') {
781
1069
  ret.push('*');
@@ -792,28 +1080,39 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
792
1080
  });
793
1081
  this.processField(meta, prop, parts.join('.'), ret, populate, joinedProps, qb);
794
1082
  }
795
- ret.unshift(...meta.primaryKeys.filter(pk => !fields.includes(pk)));
1083
+ if (!fields.includes('*') && !fields.includes(`${qb.alias}.*`)) {
1084
+ ret.unshift(...meta.primaryKeys.filter(pk => !fields.includes(pk)));
1085
+ }
796
1086
  }
797
1087
  else if (lazyProps.filter(p => !p.formula).length > 0) {
798
1088
  const props = meta.props.filter(prop => this.platform.shouldHaveColumn(prop, populate, false));
799
1089
  ret.push(...core_1.Utils.flatten(props.filter(p => !lazyProps.includes(p)).map(p => p.fieldNames)));
1090
+ addFormulas = true;
800
1091
  }
801
1092
  else if (hasLazyFormulas || requiresSQLConversion) {
802
1093
  ret.push('*');
1094
+ addFormulas = true;
803
1095
  }
804
- if (ret.length > 0 && !hasExplicitFields) {
1096
+ else {
1097
+ ret.push('*');
1098
+ }
1099
+ if (ret.length > 0 && !hasExplicitFields && addFormulas) {
805
1100
  meta.props
806
1101
  .filter(prop => prop.formula && !lazyProps.includes(prop))
807
1102
  .forEach(prop => {
808
- const alias = qb.ref(qb.alias).toString();
809
- const aliased = qb.ref(prop.fieldNames[0]).toString();
810
- ret.push(`${prop.formula(alias)} as ${aliased}`);
1103
+ const a = this.connection.getKnex().ref(alias).toString();
1104
+ const aliased = this.connection.getKnex().ref(prop.fieldNames[0]).toString();
1105
+ ret.push((0, core_1.raw)(`${prop.formula(a)} as ${aliased}`));
811
1106
  });
812
1107
  meta.props
813
- .filter(prop => prop.customType?.convertToDatabaseValueSQL || prop.customType?.convertToJSValueSQL)
1108
+ .filter(prop => prop.hasConvertToDatabaseValueSQL || prop.hasConvertToJSValueSQL)
814
1109
  .forEach(prop => ret.push(prop.name));
815
1110
  }
816
- return ret.length > 0 ? core_1.Utils.unique(ret) : ['*'];
1111
+ // add joined relations after the root entity fields
1112
+ if (joinedProps.length > 0) {
1113
+ ret.push(...this.getFieldsForJoinedLoad(qb, meta, fields, populate, options, alias));
1114
+ }
1115
+ return core_1.Utils.unique(ret);
817
1116
  }
818
1117
  }
819
1118
  exports.AbstractSqlDriver = AbstractSqlDriver;