@mikro-orm/knex 6.0.0-dev.27 → 6.0.0-dev.271

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 +588 -270
  5. package/AbstractSqlPlatform.d.ts +9 -4
  6. package/AbstractSqlPlatform.js +40 -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 +4 -8
  13. package/SqlEntityManager.js +8 -12
  14. package/SqlEntityRepository.d.ts +7 -6
  15. package/SqlEntityRepository.js +11 -8
  16. package/index.mjs +22 -3
  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 +22 -9
  22. package/query/CriteriaNodeFactory.d.ts +6 -6
  23. package/query/CriteriaNodeFactory.js +32 -16
  24. package/query/ObjectCriteriaNode.d.ts +5 -4
  25. package/query/ObjectCriteriaNode.js +72 -28
  26. package/query/QueryBuilder.d.ts +58 -26
  27. package/query/QueryBuilder.js +438 -142
  28. package/query/QueryBuilderHelper.d.ts +13 -18
  29. package/query/QueryBuilderHelper.js +191 -164
  30. package/query/ScalarCriteriaNode.d.ts +4 -3
  31. package/query/ScalarCriteriaNode.js +14 -4
  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 +26 -8
  36. package/schema/DatabaseTable.d.ts +12 -4
  37. package/schema/DatabaseTable.js +371 -46
  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,82 +161,132 @@ 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.ReferenceKind.MANY_TO_MANY, core_1.ReferenceKind.ONE_TO_MANY].includes(relation.kind)) {
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
+ let relationPojo = {};
174
221
  meta2.props
175
- .filter(prop => this.platform.shouldHaveColumn(prop, p.children || []))
222
+ .filter(prop => !ref && 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}`]);
179
- prop.fieldNames.map(name => delete root[`${relationAlias}__${name}`]);
180
228
  }
181
229
  else {
182
230
  const alias = `${relationAlias}__${prop.fieldNames[0]}`;
183
231
  relationPojo[prop.name] = root[alias];
184
- delete root[alias];
185
232
  }
186
233
  });
187
- const key = `${meta.name}-${(core_1.Utils.getCompositeKeyHash(result, meta))}`;
188
- if (map[key]) {
189
- result[relation.name] = map[key][relation.name];
234
+ const targetProps = ref
235
+ ? meta2.getPrimaryProps()
236
+ : meta2.props.filter(prop => this.platform.shouldHaveColumn(prop, hint.children || []));
237
+ for (const prop of targetProps) {
238
+ if (prop.fieldNames.length > 1) { // composite keys
239
+ const fk = prop.fieldNames.map(name => 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
+ }
246
+ else {
247
+ const alias = `${relationAlias}__${prop.fieldNames[0]}`;
248
+ relationPojo[prop.name] = root[alias];
249
+ if (prop.kind === core_1.ReferenceKind.EMBEDDED && (prop.object || meta.embeddable)) {
250
+ const item = (0, core_1.parseJsonSafe)(relationPojo[prop.name]);
251
+ if (Array.isArray(item)) {
252
+ relationPojo[prop.name] = item.map(row => row == null ? row : this.comparator.mapResult(prop.type, row));
253
+ }
254
+ else {
255
+ relationPojo[prop.name] = item == null ? item : this.comparator.mapResult(prop.type, item);
256
+ }
257
+ }
258
+ }
190
259
  }
191
- else {
192
- map[key] = result;
260
+ // properties can be mapped to multiple places, e.g. when sharing a column in multiple FKs,
261
+ // so we need to delete them after everything is mapped from given level
262
+ for (const prop of targetProps) {
263
+ prop.fieldNames.map(name => delete root[`${relationAlias}__${name}`]);
193
264
  }
194
- if ([core_1.ReferenceKind.MANY_TO_MANY, core_1.ReferenceKind.ONE_TO_MANY].includes(relation.kind)) {
195
- result[relation.name] = result[relation.name] || [];
196
- this.appendToCollection(meta2, result[relation.name], relationPojo);
265
+ if (ref) {
266
+ const tmp = Object.values(relationPojo);
267
+ /* istanbul ignore next */
268
+ relationPojo = (meta2.compositePK ? tmp : tmp[0]);
269
+ }
270
+ if ([core_1.ReferenceKind.MANY_TO_MANY, core_1.ReferenceKind.ONE_TO_MANY].includes(prop.kind)) {
271
+ result[prop.name] ??= [];
272
+ result[prop.name].push(relationPojo);
197
273
  }
198
274
  else {
199
- result[relation.name] = relationPojo;
275
+ result[prop.name] = relationPojo;
200
276
  }
201
- const populateChildren = p.children || [];
277
+ const populateChildren = hint.children || [];
202
278
  this.mapJoinedProps(relationPojo, meta2, populateChildren, qb, root, map, path);
203
279
  });
204
280
  }
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
281
  async count(entityName, where, options = {}) {
217
282
  const meta = this.metadata.find(entityName);
218
283
  if (meta?.virtual) {
219
284
  return this.countVirtual(entityName, where, options);
220
285
  }
221
286
  const qb = this.createQueryBuilder(entityName, options.ctx, options.connectionType, false)
287
+ .indexHint(options.indexHint)
288
+ .comment(options.comments)
289
+ .hintComment(options.hintComments)
222
290
  .groupBy(options.groupBy)
223
291
  .having(options.having)
224
292
  .populate(options.populate ?? [])
@@ -227,7 +295,7 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
227
295
  return this.rethrow(qb.getCount());
228
296
  }
229
297
  async nativeInsert(entityName, data, options = {}) {
230
- options.convertCustomTypes ?? (options.convertCustomTypes = true);
298
+ options.convertCustomTypes ??= true;
231
299
  const meta = this.metadata.find(entityName);
232
300
  const collections = this.extractManyToMany(entityName, data);
233
301
  const pks = meta?.primaryKeys ?? [this.config.getNamingStrategy().referenceColumnName()];
@@ -247,16 +315,20 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
247
315
  return res;
248
316
  }
249
317
  async nativeInsertMany(entityName, data, options = {}) {
250
- options.processCollections ?? (options.processCollections = true);
251
- options.convertCustomTypes ?? (options.convertCustomTypes = true);
252
- const meta = this.metadata.find(entityName);
318
+ options.processCollections ??= true;
319
+ options.convertCustomTypes ??= true;
320
+ const meta = this.metadata.find(entityName)?.root;
253
321
  const collections = options.processCollections ? data.map(d => this.extractManyToMany(entityName, d)) : [];
254
322
  const pks = this.getPrimaryKeyFields(entityName);
255
323
  const set = new Set();
256
- data.forEach(row => Object.keys(row).forEach(k => set.add(k)));
324
+ data.forEach(row => core_1.Utils.keys(row).forEach(k => set.add(k)));
257
325
  const props = [...set].map(name => meta?.properties[name] ?? { name, fieldNames: [name] });
258
- const fields = core_1.Utils.flatten(props.map(prop => prop.fieldNames));
326
+ let fields = core_1.Utils.flatten(props.map(prop => prop.fieldNames));
327
+ const duplicates = core_1.Utils.findDuplicates(fields);
259
328
  const params = [];
329
+ if (duplicates.length) {
330
+ fields = core_1.Utils.unique(fields);
331
+ }
260
332
  /* istanbul ignore next */
261
333
  const tableName = meta ? this.getTableName(meta, options) : this.platform.quoteIdentifier(entityName);
262
334
  let sql = `insert into ${tableName} `;
@@ -267,29 +339,62 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
267
339
  else {
268
340
  sql += ' ' + data.map(() => `select null as ${this.platform.quoteIdentifier(pks[0])}`).join(' union all ');
269
341
  }
342
+ const addParams = (prop, row) => {
343
+ let value = row[prop.name];
344
+ if (prop.kind === core_1.ReferenceKind.EMBEDDED && prop.object) {
345
+ if (prop.array) {
346
+ for (let i = 0; i < value.length; i++) {
347
+ const item = value[i];
348
+ value[i] = this.mapDataToFieldNames(item, false, prop.embeddedProps, options.convertCustomTypes);
349
+ }
350
+ }
351
+ else {
352
+ value = this.mapDataToFieldNames(value, false, prop.embeddedProps, options.convertCustomTypes);
353
+ }
354
+ }
355
+ if (options.convertCustomTypes && prop.customType) {
356
+ params.push(prop.customType.convertToDatabaseValue(value, this.platform, { key: prop.name, mode: 'query-data' }));
357
+ return;
358
+ }
359
+ params.push(value);
360
+ };
270
361
  if (fields.length > 0 || this.platform.usesDefaultKeyword()) {
271
362
  sql += data.map(row => {
272
363
  const keys = [];
364
+ const usedDups = [];
273
365
  props.forEach(prop => {
274
366
  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]);
367
+ const param = core_1.Utils.flatten([...row[prop.name] ?? prop.fieldNames.map(() => null)]);
368
+ const key = param.map(() => '?');
369
+ prop.fieldNames.forEach((field, idx) => {
370
+ if (!duplicates.includes(field) || !usedDups.includes(field)) {
371
+ params.push(param[idx]);
372
+ keys.push(key[idx]);
373
+ usedDups.push(field);
374
+ }
375
+ });
281
376
  }
282
377
  else {
283
- params.push(row[prop.name]);
284
- keys.push('?');
378
+ const field = prop.fieldNames[0];
379
+ if (!duplicates.includes(field) || !usedDups.includes(field)) {
380
+ if (prop.customType && 'convertToDatabaseValueSQL' in prop.customType && !this.platform.isRaw(row[prop.name])) {
381
+ keys.push(prop.customType.convertToDatabaseValueSQL('?', this.platform));
382
+ }
383
+ else {
384
+ keys.push('?');
385
+ }
386
+ addParams(prop, row);
387
+ usedDups.push(field);
388
+ }
285
389
  }
286
390
  });
287
391
  return '(' + (keys.join(', ') || 'default') + ')';
288
392
  }).join(', ');
289
393
  }
290
- if (this.platform.usesReturningStatement()) {
291
- /* istanbul ignore next */
292
- const returningProps = meta.hydrateProps.filter(prop => prop.persist !== false && (prop.primary || prop.defaultRaw));
394
+ if (meta && this.platform.usesReturningStatement()) {
395
+ const returningProps = meta.props
396
+ .filter(prop => prop.persist !== false && prop.defaultRaw || prop.autoincrement || prop.generated)
397
+ .filter(prop => !(prop.name in data[0]) || core_1.Utils.isRawSql(data[0][prop.name]));
293
398
  const returningFields = core_1.Utils.flatten(returningProps.map(prop => prop.fieldNames));
294
399
  /* istanbul ignore next */
295
400
  sql += returningFields.length > 0 ? ` returning ${returningFields.map(field => this.platform.quoteIdentifier(field)).join(', ')}` : '';
@@ -301,8 +406,8 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
301
406
  pk = data.map(d => core_1.Utils.getPrimaryKeyCond(d, pks));
302
407
  }
303
408
  else {
304
- res.row ?? (res.row = {});
305
- res.rows ?? (res.rows = []);
409
+ res.row ??= {};
410
+ res.rows ??= [];
306
411
  pk = data.map((d, i) => d[pks[0]] ?? res.rows[i]?.[pks[0]]).map(d => [d]);
307
412
  res.insertId = res.insertId || res.row[pks[0]];
308
413
  }
@@ -312,7 +417,7 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
312
417
  return res;
313
418
  }
314
419
  async nativeUpdate(entityName, where, data, options = {}) {
315
- options.convertCustomTypes ?? (options.convertCustomTypes = true);
420
+ options.convertCustomTypes ??= true;
316
421
  const meta = this.metadata.find(entityName);
317
422
  const pks = this.getPrimaryKeyFields(entityName);
318
423
  const collections = this.extractManyToMany(entityName, data);
@@ -326,14 +431,27 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
326
431
  .withSchema(this.getSchemaName(meta, options));
327
432
  if (options.upsert) {
328
433
  /* istanbul ignore next */
329
- const uniqueFields = core_1.Utils.isPlainObject(where) ? Object.keys(where) : meta.primaryKeys;
330
- /* istanbul ignore next */
434
+ const uniqueFields = options.onConflictFields ?? (core_1.Utils.isPlainObject(where) ? core_1.Utils.keys(where) : meta.primaryKeys);
435
+ const returning = (0, core_1.getOnConflictReturningFields)(meta, data, uniqueFields, options);
331
436
  qb.insert(data)
332
- .onConflict(uniqueFields.map(p => meta?.properties[p]?.fieldNames[0] ?? p))
333
- .merge(Object.keys(data).filter(f => !uniqueFields.includes(f)));
437
+ .onConflict(uniqueFields)
438
+ .returning(returning);
439
+ if (!options.onConflictAction || options.onConflictAction === 'merge') {
440
+ const fields = (0, core_1.getOnConflictFields)(data, uniqueFields, options);
441
+ qb.merge(fields);
442
+ }
443
+ if (options.onConflictAction === 'ignore') {
444
+ qb.ignore();
445
+ }
334
446
  }
335
447
  else {
336
448
  qb.update(data).where(where);
449
+ // reload generated columns and version fields
450
+ const returning = [];
451
+ meta?.props
452
+ .filter(prop => (prop.generated && !prop.primary) || prop.version)
453
+ .forEach(prop => returning.push(prop.name));
454
+ qb.returning(returning);
337
455
  }
338
456
  res = await this.rethrow(qb.execute('run', false));
339
457
  }
@@ -343,26 +461,65 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
343
461
  return res;
344
462
  }
345
463
  async nativeUpdateMany(entityName, where, data, options = {}) {
346
- options.processCollections ?? (options.processCollections = true);
347
- options.convertCustomTypes ?? (options.convertCustomTypes = true);
464
+ options.processCollections ??= true;
465
+ options.convertCustomTypes ??= true;
348
466
  const meta = this.metadata.get(entityName);
349
467
  if (options.upsert) {
350
- const uniqueFields = core_1.Utils.isPlainObject(where[0]) ? Object.keys(where[0]) : meta.primaryKeys;
468
+ const uniqueFields = options.onConflictFields ?? (core_1.Utils.isPlainObject(where[0]) ? Object.keys(where[0]).flatMap(key => core_1.Utils.splitPrimaryKeys(key)) : meta.primaryKeys);
351
469
  const qb = this.createQueryBuilder(entityName, options.ctx, 'write', options.convertCustomTypes).withSchema(this.getSchemaName(meta, options));
470
+ const returning = (0, core_1.getOnConflictReturningFields)(meta, data[0], uniqueFields, options);
352
471
  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);
472
+ .onConflict(uniqueFields)
473
+ .returning(returning);
474
+ if (!options.onConflictAction || options.onConflictAction === 'merge') {
475
+ const fields = (0, core_1.getOnConflictFields)(data[0], uniqueFields, options);
476
+ qb.merge(fields);
477
+ }
478
+ if (options.onConflictAction === 'ignore') {
479
+ qb.ignore();
480
+ }
481
+ return this.rethrow(qb.execute('run', false));
356
482
  }
357
483
  const collections = options.processCollections ? data.map(d => this.extractManyToMany(entityName, d)) : [];
358
484
  const keys = new Set();
359
- data.forEach(row => Object.keys(row).forEach(k => keys.add(k)));
485
+ const fields = new Set();
486
+ const returning = new Set();
487
+ data.forEach(row => {
488
+ core_1.Utils.keys(row).forEach(k => {
489
+ keys.add(k);
490
+ if (core_1.Utils.isRawSql(row[k])) {
491
+ returning.add(k);
492
+ }
493
+ });
494
+ });
495
+ // reload generated columns and version fields
496
+ meta?.props
497
+ .filter(prop => (prop.generated && !prop.primary) || prop.version)
498
+ .forEach(prop => returning.add(prop.name));
360
499
  const pkCond = core_1.Utils.flatten(meta.primaryKeys.map(pk => meta.properties[pk].fieldNames)).map(pk => `${this.platform.quoteIdentifier(pk)} = ?`).join(' and ');
361
500
  const params = [];
362
501
  let sql = `update ${this.getTableName(meta, options)} set `;
502
+ const addParams = (prop, value) => {
503
+ if (prop.kind === core_1.ReferenceKind.EMBEDDED && prop.object) {
504
+ if (prop.array) {
505
+ for (let i = 0; i < value.length; i++) {
506
+ const item = value[i];
507
+ value[i] = this.mapDataToFieldNames(item, false, prop.embeddedProps, options.convertCustomTypes);
508
+ }
509
+ }
510
+ else {
511
+ value = this.mapDataToFieldNames(value, false, prop.embeddedProps, options.convertCustomTypes);
512
+ }
513
+ }
514
+ params.push(value);
515
+ };
363
516
  keys.forEach(key => {
364
517
  const prop = meta.properties[key];
365
518
  prop.fieldNames.forEach((fieldName, fieldNameIdx) => {
519
+ if (fields.has(fieldName)) {
520
+ return;
521
+ }
522
+ fields.add(fieldName);
366
523
  sql += `${this.platform.quoteIdentifier(fieldName)} = case`;
367
524
  where.forEach((cond, idx) => {
368
525
  if (key in data[idx]) {
@@ -374,7 +531,8 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
374
531
  else {
375
532
  sql += '?';
376
533
  }
377
- params.push(...pks, prop.fieldNames.length > 1 ? data[idx][key]?.[fieldNameIdx] : data[idx][key]);
534
+ params.push(...pks);
535
+ addParams(prop, prop.fieldNames.length > 1 ? data[idx][key]?.[fieldNameIdx] : data[idx][key]);
378
536
  }
379
537
  });
380
538
  sql += ` else ${this.platform.quoteIdentifier(fieldName)} end, `;
@@ -385,7 +543,7 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
385
543
  const versionProperty = meta.properties[meta.versionProperty];
386
544
  const quotedFieldName = this.platform.quoteIdentifier(versionProperty.fieldNames[0]);
387
545
  sql += `${quotedFieldName} = `;
388
- if (versionProperty.type.toLowerCase() === 'date') {
546
+ if (versionProperty.runtimeType === 'Date') {
389
547
  sql += this.platform.getCurrentTimestampSQL(versionProperty.length);
390
548
  }
391
549
  else {
@@ -416,6 +574,11 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
416
574
  return '?';
417
575
  });
418
576
  sql += ` in (${conds.join(', ')})`;
577
+ if (this.platform.usesReturningStatement() && returning.size > 0) {
578
+ const returningFields = core_1.Utils.flatten([...returning].map(prop => meta.properties[prop].fieldNames));
579
+ /* istanbul ignore next */
580
+ sql += returningFields.length > 0 ? ` returning ${returningFields.map(field => this.platform.quoteIdentifier(field)).join(', ')}` : '';
581
+ }
419
582
  const res = await this.rethrow(this.execute(sql, params, 'run', options.ctx));
420
583
  for (let i = 0; i < collections.length; i++) {
421
584
  await this.processManyToMany(meta, where[i], collections[i], false, options);
@@ -431,49 +594,81 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
431
594
  const qb = this.createQueryBuilder(entityName, options.ctx, 'write', false).delete(where).withSchema(this.getSchemaName(meta, options));
432
595
  return this.rethrow(qb.execute('run', false));
433
596
  }
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.kind === core_1.ReferenceKind.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 === '*') {
597
+ async syncCollections(collections, options) {
598
+ const groups = {};
599
+ for (const coll of collections) {
600
+ const wrapped = (0, core_1.helper)(coll.owner);
601
+ const meta = wrapped.__meta;
602
+ const pks = wrapped.getPrimaryKeys(true);
603
+ const snap = coll.getSnapshot();
604
+ const includes = (arr, item) => !!arr.find(i => core_1.Utils.equals(i, item));
605
+ const snapshot = snap ? snap.map(item => (0, core_1.helper)(item).getPrimaryKeys(true)) : [];
606
+ const current = coll.getItems(false).map(item => (0, core_1.helper)(item).getPrimaryKeys(true));
607
+ const deleteDiff = snap ? snapshot.filter(item => !includes(current, item)) : true;
608
+ const insertDiff = current.filter(item => !includes(snapshot, item));
609
+ const target = snapshot.filter(item => includes(current, item)).concat(...insertDiff);
610
+ const equals = core_1.Utils.equals(current, target);
611
+ // wrong order if we just delete and insert to the end (only owning sides can have fixed order)
612
+ if (coll.property.owner && coll.property.fixedOrder && !equals && Array.isArray(deleteDiff)) {
613
+ deleteDiff.length = insertDiff.length = 0;
614
+ deleteDiff.push(...snapshot);
615
+ insertDiff.push(...current);
616
+ }
617
+ if (coll.property.kind === core_1.ReferenceKind.ONE_TO_MANY) {
618
+ const cols = coll.property.referencedColumnNames;
619
+ const qb = this.createQueryBuilder(coll.property.type, options?.ctx, 'write')
620
+ .withSchema(this.getSchemaName(meta, options));
621
+ if (coll.getSnapshot() === undefined) {
622
+ if (coll.property.orphanRemoval) {
623
+ const kqb = qb.delete({ [coll.property.mappedBy]: pks })
624
+ .getKnexQuery()
625
+ .whereNotIn(cols, insertDiff);
626
+ return this.rethrow(this.execute(kqb));
627
+ }
628
+ const kqb = qb.update({ [coll.property.mappedBy]: null })
629
+ .getKnexQuery()
630
+ .whereNotIn(cols, insertDiff);
631
+ return this.rethrow(this.execute(kqb));
632
+ }
633
+ const kqb = qb.update({ [coll.property.mappedBy]: pks })
634
+ .getKnexQuery()
635
+ .whereIn(cols, insertDiff);
636
+ return this.rethrow(this.execute(kqb));
637
+ }
466
638
  /* istanbul ignore next */
467
- options ?? (options = {});
468
- options.schema = ownerSchema;
639
+ const pivotMeta = this.metadata.find(coll.property.pivotEntity);
640
+ let schema = pivotMeta.schema;
641
+ if (schema === '*') {
642
+ const ownerSchema = wrapped.getSchema() === '*' ? this.config.get('schema') : wrapped.getSchema();
643
+ schema = coll.property.owner ? ownerSchema : this.config.get('schema');
644
+ }
645
+ else if (schema == null) {
646
+ schema = this.config.get('schema');
647
+ }
648
+ const tableName = `${schema ?? '_'}.${pivotMeta.tableName}`;
649
+ const persister = groups[tableName] ??= new PivotCollectionPersister_1.PivotCollectionPersister(pivotMeta, this, options?.ctx, schema);
650
+ persister.enqueueUpdate(coll.property, insertDiff, deleteDiff, pks);
651
+ }
652
+ for (const persister of core_1.Utils.values(groups)) {
653
+ await this.rethrow(persister.execute());
469
654
  }
470
- return this.rethrow(this.updateCollectionDiff(meta, coll.property, pks, deleteDiff, insertDiff, options));
471
655
  }
472
- async loadFromPivotTable(prop, owners, where = {}, orderBy, ctx, options) {
473
- const pivotProp2 = this.getPivotInverseProperty(prop);
474
- const ownerMeta = this.metadata.find(pivotProp2.type);
656
+ async loadFromPivotTable(prop, owners, where = {}, orderBy, ctx, options, pivotJoin) {
475
657
  const pivotMeta = this.metadata.find(prop.pivotEntity);
476
- const cond = { [`${prop.pivotEntity}.${pivotProp2.name}`]: { $in: ownerMeta.compositePK ? owners : owners.map(o => o[0]) } };
658
+ const pivotProp1 = pivotMeta.relations[prop.owner ? 1 : 0];
659
+ const pivotProp2 = pivotMeta.relations[prop.owner ? 0 : 1];
660
+ const ownerMeta = this.metadata.find(pivotProp2.type);
661
+ options = { ...options };
662
+ const qb = this.createQueryBuilder(prop.pivotEntity, ctx, options.connectionType)
663
+ .withSchema(this.getSchemaName(pivotMeta, options))
664
+ .indexHint(options.indexHint)
665
+ .comment(options.comments)
666
+ .hintComment(options.hintComments);
667
+ const pivotAlias = qb.alias;
668
+ const pivotKey = pivotProp2.joinColumns.map(column => `${pivotAlias}.${column}`).join(core_1.Utils.PK_SEPARATOR);
669
+ const cond = {
670
+ [pivotKey]: { $in: ownerMeta.compositePK ? owners : owners.map(o => o[0]) },
671
+ };
477
672
  /* istanbul ignore if */
478
673
  if (!core_1.Utils.isEmpty(where) && Object.keys(where).every(k => core_1.Utils.isOperator(k, false))) {
479
674
  where = cond;
@@ -481,29 +676,51 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
481
676
  else {
482
677
  where = { ...where, ...cond };
483
678
  }
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;
679
+ orderBy = this.getPivotOrderBy(prop, pivotProp1, pivotAlias, orderBy);
680
+ const populate = this.autoJoinOneToOneOwner(prop.targetMeta, []);
681
+ const fields = [];
682
+ const k1 = !prop.owner ? 'joinColumns' : 'inverseJoinColumns';
683
+ const k2 = prop.owner ? 'joinColumns' : 'inverseJoinColumns';
684
+ const cols = [
685
+ ...prop[k1].map(col => `${pivotAlias}.${col} as fk__${col}`),
686
+ ...prop[k2].map(col => `${pivotAlias}.${col} as fk__${col}`),
687
+ ];
688
+ fields.push(...cols);
689
+ if (!pivotJoin) {
690
+ const targetAlias = qb.getNextAlias(prop.targetMeta.tableName);
691
+ const targetSchema = this.getSchemaName(prop.targetMeta, options) ?? this.platform.getDefaultSchemaName();
692
+ qb.innerJoin(pivotProp1.name, targetAlias, {}, targetSchema);
693
+ const targetFields = this.buildFields(prop.targetMeta, (options.populate ?? []), [], qb, targetAlias, options.fields, options);
694
+ for (const field of targetFields) {
695
+ const f = field.toString();
696
+ fields.unshift(f.includes('.') ? field : `${targetAlias}.${f}`);
697
+ }
698
+ // we need to handle 1:1 owner auto-joins explicitly, as the QB type is the pivot table, not the target
699
+ populate.forEach(hint => {
700
+ const alias = qb.getNextAlias(prop.targetMeta.tableName);
701
+ qb.leftJoin(`${targetAlias}.${hint.field}`, alias);
702
+ // eslint-disable-next-line dot-notation
703
+ Object.values(qb['_joins']).forEach(join => {
704
+ const [propName] = hint.field.split(':', 2);
705
+ if (join.alias === alias && join.prop.name === propName) {
706
+ fields.push(...qb.helper.mapJoinColumns(qb.type, join));
707
+ }
708
+ });
500
709
  });
501
710
  }
502
- const items = owners.length ? await this.rethrow(qb.execute('all')) : [];
711
+ qb.select(fields)
712
+ .where({ [pivotProp1.name]: where })
713
+ .orderBy(orderBy)
714
+ .setLockMode(options.lockMode, options.lockTableAliases);
715
+ if (owners.length === 1 && (options.offset != null || options.limit != null)) {
716
+ qb.limit(options.limit, options.offset);
717
+ }
718
+ const res = owners.length ? await this.rethrow(qb.execute('all', { mergeResults: false, mapResults: false })) : [];
719
+ const items = res.map((row) => super.mapResult(row, prop.targetMeta));
503
720
  const map = {};
504
721
  const pkProps = ownerMeta.getPrimaryProps();
505
722
  owners.forEach(owner => {
506
- const key = core_1.Utils.getPrimaryKeyHash(prop.joinColumns.map((col, idx) => {
723
+ const key = core_1.Utils.getPrimaryKeyHash(prop.joinColumns.map((_col, idx) => {
507
724
  const pkProp = pkProps[idx];
508
725
  return pkProp.customType ? pkProp.customType.convertToJSValue(owner[idx], this.platform) : owner[idx];
509
726
  }));
@@ -516,10 +733,25 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
516
733
  }));
517
734
  map[key].push(item);
518
735
  prop.joinColumns.forEach(col => delete item[`fk__${col}`]);
519
- prop.inverseJoinColumns.forEach(col => delete item[`fk__${col}`]);
736
+ prop.inverseJoinColumns.forEach((col, idx) => {
737
+ core_1.Utils.renameKey(item, `fk__${col}`, prop.targetMeta.primaryKeys[idx]);
738
+ });
520
739
  });
521
740
  return map;
522
741
  }
742
+ getPivotOrderBy(prop, pivotProp, pivotAlias, orderBy) {
743
+ // FIXME this is ignoring the rest of the array items
744
+ if (!core_1.Utils.isEmpty(orderBy)) {
745
+ return [{ [pivotProp.name]: core_1.Utils.asArray(orderBy)[0] }];
746
+ }
747
+ if (!core_1.Utils.isEmpty(prop.orderBy)) {
748
+ return [{ [pivotProp.name]: core_1.Utils.asArray(prop.orderBy)[0] }];
749
+ }
750
+ if (prop.fixedOrder) {
751
+ return [{ [`${pivotAlias}.${prop.fixedOrderColumn}`]: core_1.QueryOrder.ASC }];
752
+ }
753
+ return [];
754
+ }
523
755
  async execute(queryOrKnex, params = [], method = 'all', ctx) {
524
756
  return this.rethrow(this.connection.execute(queryOrKnex, params, method, ctx));
525
757
  }
@@ -534,37 +766,66 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
534
766
  const toPopulate = meta.relations
535
767
  .filter(prop => prop.kind === core_1.ReferenceKind.ONE_TO_ONE && !prop.owner && !relationsToPopulate.includes(prop.name))
536
768
  .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 }));
769
+ .map(prop => ({ field: `${prop.name}:ref`, strategy: prop.strategy }));
538
770
  return [...populate, ...toPopulate];
539
771
  }
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.kind !== core_1.ReferenceKind.SCALAR;
772
+ /**
773
+ * @internal
774
+ */
775
+ joinedProps(meta, populate, options) {
776
+ return populate.filter(hint => {
777
+ const [propName, ref] = hint.field.split(':', 2);
778
+ const prop = meta.properties[propName] || {};
779
+ if ((options?.strategy || hint.strategy || prop.strategy || this.config.get('loadStrategy')) !== core_1.LoadStrategy.JOINED) {
780
+ return false;
781
+ }
782
+ if (ref) {
783
+ return [core_1.ReferenceKind.MANY_TO_MANY, core_1.ReferenceKind.ONE_TO_MANY].includes(prop.kind);
784
+ }
785
+ return ![core_1.ReferenceKind.SCALAR, core_1.ReferenceKind.EMBEDDED].includes(prop.kind);
544
786
  });
545
787
  }
546
788
  /**
547
789
  * @internal
548
790
  */
549
- mergeJoinedResult(rawResults, meta) {
550
- // group by the root entity primary key first
551
- const map = {};
791
+ mergeJoinedResult(rawResults, meta, joinedProps) {
552
792
  const res = [];
553
- rawResults.forEach(item => {
793
+ const map = {};
794
+ for (const item of rawResults) {
554
795
  const pk = core_1.Utils.getCompositeKeyHash(item, meta);
555
796
  if (map[pk]) {
556
- map[pk].push(item);
797
+ for (const hint of joinedProps) {
798
+ const [propName, ref] = hint.field.split(':', 2);
799
+ const prop = meta.properties[propName];
800
+ if (!item[propName]) {
801
+ continue;
802
+ }
803
+ if (prop.kind === core_1.ReferenceKind.MANY_TO_MANY && ref) {
804
+ map[pk][propName] = [...map[pk][propName], ...item[propName]];
805
+ continue;
806
+ }
807
+ switch (prop.kind) {
808
+ case core_1.ReferenceKind.ONE_TO_MANY:
809
+ case core_1.ReferenceKind.MANY_TO_MANY:
810
+ map[pk][propName] = this.mergeJoinedResult([...map[pk][propName], ...item[propName]], prop.targetMeta, hint.children ?? []);
811
+ break;
812
+ case core_1.ReferenceKind.MANY_TO_ONE:
813
+ case core_1.ReferenceKind.ONE_TO_ONE:
814
+ map[pk][propName] = this.mergeJoinedResult([map[pk][propName], item[propName]], prop.targetMeta, hint.children ?? [])[0];
815
+ break;
816
+ }
817
+ }
557
818
  }
558
819
  else {
559
- map[pk] = [item];
820
+ map[pk] = item;
560
821
  res.push(item);
561
822
  }
562
- });
823
+ }
563
824
  return res;
564
825
  }
565
- getFieldsForJoinedLoad(qb, meta, explicitFields, populate = [], parentTableAlias, parentJoinPath) {
826
+ getFieldsForJoinedLoad(qb, meta, explicitFields, populate = [], options, parentTableAlias, parentJoinPath) {
566
827
  const fields = [];
567
- const joinedProps = this.joinedProps(meta, populate);
828
+ const joinedProps = this.joinedProps(meta, populate, options);
568
829
  if (explicitFields?.includes('*')) {
569
830
  fields.push('*');
570
831
  }
@@ -575,26 +836,44 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
575
836
  if (!fields || prop.primary) {
576
837
  return !fields?.includes('*');
577
838
  }
578
- return fields.includes(prop.name);
839
+ return fields.some(f => f === prop.name || f.toString().startsWith(prop.name + '.'));
579
840
  };
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];
841
+ const populateWhereAll = options?._populateWhere === 'all' || core_1.Utils.isEmpty(options?._populateWhere);
842
+ // root entity is already handled, skip that
843
+ if (parentJoinPath) {
844
+ // alias all fields in the primary table
845
+ meta.props
846
+ .filter(prop => shouldHaveColumn(prop, populate, explicitFields))
847
+ .forEach(prop => fields.push(...this.mapPropToFieldNames(qb, prop, parentTableAlias)));
848
+ }
849
+ joinedProps.forEach(hint => {
850
+ const [propName, ref] = hint.field.split(':', 2);
851
+ const prop = meta.properties[propName];
586
852
  const meta2 = this.metadata.find(prop.type);
853
+ const pivotRefJoin = prop.kind === core_1.ReferenceKind.MANY_TO_MANY && ref;
587
854
  const tableAlias = qb.getNextAlias(prop.name);
588
855
  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);
856
+ let path = parentJoinPath ? `${parentJoinPath}.${prop.name}` : `${meta.name}.${prop.name}`;
857
+ if (!parentJoinPath && populateWhereAll && !path.startsWith('[populate]')) {
858
+ path = '[populate]' + path;
859
+ }
860
+ const joinType = pivotRefJoin ? query_1.JoinType.pivotJoin : query_1.JoinType.leftJoin;
861
+ qb.join(field, tableAlias, {}, joinType, path);
862
+ if (pivotRefJoin) {
863
+ 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}`)));
864
+ }
865
+ if (prop.kind === core_1.ReferenceKind.ONE_TO_MANY && ref) {
866
+ fields.push(...this.getFieldsForJoinedLoad(qb, meta2, prop.referencedColumnNames, hint.children, options, tableAlias, path));
867
+ }
591
868
  const childExplicitFields = explicitFields?.filter(f => core_1.Utils.isPlainObject(f)).map(o => o[prop.name])[0] || [];
592
869
  explicitFields?.forEach(f => {
593
870
  if (typeof f === 'string' && f.startsWith(`${prop.name}.`)) {
594
871
  childExplicitFields.push(f.substring(prop.name.length + 1));
595
872
  }
596
873
  });
597
- fields.push(...this.getFieldsForJoinedLoad(qb, meta2, childExplicitFields.length === 0 ? undefined : childExplicitFields, relation.children, tableAlias, path));
874
+ if (!ref) {
875
+ fields.push(...this.getFieldsForJoinedLoad(qb, meta2, childExplicitFields.length === 0 ? undefined : childExplicitFields, hint.children, options, tableAlias, path));
876
+ }
598
877
  });
599
878
  return fields;
600
879
  }
@@ -602,24 +881,35 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
602
881
  * @internal
603
882
  */
604
883
  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()];
884
+ const knex = this.connection.getKnex();
885
+ const aliased = knex.ref(tableAlias ? `${tableAlias}__${prop.fieldNames[0]}` : prop.fieldNames[0]).toString();
886
+ if (tableAlias && prop.customTypes?.some(type => type?.convertToJSValueSQL)) {
887
+ return prop.fieldNames.map((col, idx) => {
888
+ if (!prop.customTypes[idx]?.convertToJSValueSQL) {
889
+ return col;
890
+ }
891
+ const prefixed = knex.ref(col).withSchema(tableAlias).toString();
892
+ const aliased = knex.ref(`${tableAlias}__${col}`).toString();
893
+ return (0, core_1.raw)(`${prop.customTypes[idx].convertToJSValueSQL(prefixed, this.platform)} as ${aliased}`);
894
+ });
895
+ }
896
+ if (tableAlias && prop.customType?.convertToJSValueSQL) {
897
+ const prefixed = knex.ref(prop.fieldNames[0]).withSchema(tableAlias).toString();
898
+ return [(0, core_1.raw)(`${prop.customType.convertToJSValueSQL(prefixed, this.platform)} as ${aliased}`)];
609
899
  }
610
900
  if (prop.formula) {
611
- const alias = qb.ref(tableAlias ?? qb.alias).toString();
612
- return [`${prop.formula(alias)} as ${aliased}`];
901
+ const alias = knex.ref(tableAlias ?? qb.alias).toString();
902
+ return [(0, core_1.raw)(`${prop.formula(alias)} as ${aliased}`)];
613
903
  }
614
904
  if (tableAlias) {
615
- return prop.fieldNames.map(fieldName => qb.ref(fieldName).withSchema(tableAlias).as(`${tableAlias}__${fieldName}`));
905
+ return prop.fieldNames.map(fieldName => knex.ref(fieldName).withSchema(tableAlias).as(`${tableAlias}__${fieldName}`));
616
906
  }
617
907
  return prop.fieldNames;
618
908
  }
619
909
  /** @internal */
620
- createQueryBuilder(entityName, ctx, preferredConnectionType, convertCustomTypes) {
910
+ createQueryBuilder(entityName, ctx, preferredConnectionType, convertCustomTypes, loggerContext) {
621
911
  const connectionType = this.resolveConnectionType({ ctx, connectionType: preferredConnectionType });
622
- const qb = new query_1.QueryBuilder(entityName, this.metadata, this, ctx, undefined, connectionType);
912
+ const qb = new query_1.QueryBuilder(entityName, this.metadata, this, ctx, undefined, connectionType, undefined, loggerContext);
623
913
  if (!convertCustomTypes) {
624
914
  qb.unsetFlag(core_1.QueryFlag.CONVERT_CUSTOM_TYPES);
625
915
  }
@@ -656,77 +946,97 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
656
946
  }
657
947
  for (const prop of meta.relations) {
658
948
  if (collections[prop.name]) {
659
- await this.rethrow(this.updateCollectionDiff(meta, prop, pks, clear, collections[prop.name], options));
949
+ const pivotMeta = this.metadata.find(prop.pivotEntity);
950
+ const persister = new PivotCollectionPersister_1.PivotCollectionPersister(pivotMeta, this, options?.ctx, options?.schema);
951
+ persister.enqueueUpdate(prop, collections[prop.name], clear, pks);
952
+ await this.rethrow(persister.execute());
660
953
  }
661
954
  }
662
955
  }
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
956
  async lockPessimistic(entity, options) {
706
957
  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);
958
+ const qb = this.createQueryBuilder(entity.constructor.name, options.ctx).withSchema(options.schema ?? meta.schema);
708
959
  const cond = core_1.Utils.getPrimaryKeyCond(entity, meta.primaryKeys);
709
- qb.select('1').where(cond).setLockMode(options.lockMode, options.lockTableAliases);
960
+ qb.select((0, core_1.raw)('1')).where(cond).setLockMode(options.lockMode, options.lockTableAliases);
710
961
  await this.rethrow(qb.execute());
711
962
  }
712
- buildJoinedPropsOrderBy(entityName, qb, meta, populate, parentPath) {
963
+ buildOrderBy(qb, meta, populate, options) {
964
+ const joinedProps = this.joinedProps(meta, populate, options);
965
+ // `options._populateWhere` is a copy of the value provided by user with a fallback to the global config option
966
+ // as `options.populateWhere` will be always recomputed to respect filters
967
+ const populateWhereAll = options._populateWhere !== 'infer' && !core_1.Utils.isEmpty(options._populateWhere);
968
+ const path = (populateWhereAll ? '[populate]' : '') + meta.className;
969
+ const populateOrderBy = this.buildPopulateOrderBy(qb, meta, core_1.Utils.asArray(options.populateOrderBy ?? options.orderBy), path);
970
+ const joinedPropsOrderBy = this.buildJoinedPropsOrderBy(qb, meta, joinedProps, options, path);
971
+ return [...core_1.Utils.asArray(options.orderBy), ...populateOrderBy, ...joinedPropsOrderBy];
972
+ }
973
+ buildPopulateOrderBy(qb, meta, populateOrderBy, parentPath, parentAlias = qb.alias) {
713
974
  const orderBy = [];
714
- const joinedProps = this.joinedProps(meta, populate);
715
- joinedProps.forEach(relation => {
716
- const prop = meta.properties[relation.field];
975
+ for (let i = 0; i < populateOrderBy.length; i++) {
976
+ const orderHint = populateOrderBy[i];
977
+ for (const propName of core_1.Utils.keys(orderHint)) {
978
+ const prop = meta.properties[propName];
979
+ const meta2 = this.metadata.find(prop.type);
980
+ const childOrder = orderHint[prop.name];
981
+ let path = `${parentPath}.${propName}`;
982
+ if (prop.kind === core_1.ReferenceKind.MANY_TO_MANY && typeof childOrder !== 'object') {
983
+ path += '[pivot]';
984
+ }
985
+ const join = qb.getJoinForPath(path, { matchPopulateJoins: true });
986
+ const propAlias = qb.getAliasForJoinPath(join ?? path, { matchPopulateJoins: true }) ?? parentAlias;
987
+ if (!join && parentAlias === qb.alias) {
988
+ continue;
989
+ }
990
+ if (![core_1.ReferenceKind.SCALAR, core_1.ReferenceKind.EMBEDDED].includes(prop.kind) && typeof childOrder === 'object') {
991
+ const children = this.buildPopulateOrderBy(qb, meta2, core_1.Utils.asArray(childOrder), path, propAlias);
992
+ orderBy.push(...children);
993
+ continue;
994
+ }
995
+ if (prop.kind === core_1.ReferenceKind.MANY_TO_MANY && join) {
996
+ if (prop.fixedOrderColumn) {
997
+ orderBy.push({ [`${join.alias}.${prop.fixedOrderColumn}`]: childOrder });
998
+ }
999
+ else {
1000
+ for (const col of prop.inverseJoinColumns) {
1001
+ orderBy.push({ [`${join.ownerAlias}.${col}`]: childOrder });
1002
+ }
1003
+ }
1004
+ continue;
1005
+ }
1006
+ const order = typeof childOrder === 'object' ? childOrder[propName] : childOrder;
1007
+ orderBy.push({ [`${propAlias}.${propName}`]: order });
1008
+ }
1009
+ }
1010
+ return orderBy;
1011
+ }
1012
+ buildJoinedPropsOrderBy(qb, meta, populate, options, parentPath) {
1013
+ const orderBy = [];
1014
+ const joinedProps = this.joinedProps(meta, populate, options);
1015
+ for (const hint of joinedProps) {
1016
+ const [propName, ref] = hint.field.split(':', 2);
1017
+ const prop = meta.properties[propName];
717
1018
  const propOrderBy = prop.orderBy;
718
- const path = `${parentPath ? parentPath : entityName}.${relation.field}`;
719
- const propAlias = qb.getAliasForJoinPath(path);
1019
+ let path = `${parentPath}.${propName}`;
1020
+ if (prop.kind === core_1.ReferenceKind.MANY_TO_MANY && ref) {
1021
+ path += '[pivot]';
1022
+ }
1023
+ const join = qb.getJoinForPath(path, { matchPopulateJoins: true });
1024
+ const propAlias = qb.getAliasForJoinPath(join ?? path, { matchPopulateJoins: true });
1025
+ const meta2 = this.metadata.find(prop.type);
1026
+ if (prop.kind === core_1.ReferenceKind.MANY_TO_MANY && prop.fixedOrder && join) {
1027
+ const alias = ref ? propAlias : join.ownerAlias;
1028
+ orderBy.push({ [`${alias}.${prop.fixedOrderColumn}`]: core_1.QueryOrder.ASC });
1029
+ }
720
1030
  if (propOrderBy) {
721
- Object.keys(propOrderBy).forEach(field => {
1031
+ core_1.Utils.keys(propOrderBy).forEach(field => {
722
1032
  orderBy.push({ [`${propAlias}.${field}`]: propOrderBy[field] });
723
1033
  });
724
1034
  }
725
- if (relation.children) {
726
- const meta2 = this.metadata.find(prop.type);
727
- orderBy.push(...this.buildJoinedPropsOrderBy(prop.name, qb, meta2, relation.children, path));
1035
+ if (hint.children) {
1036
+ const buildJoinedPropsOrderBy = this.buildJoinedPropsOrderBy(qb, meta2, hint.children, options, path);
1037
+ orderBy.push(...buildJoinedPropsOrderBy);
728
1038
  }
729
- });
1039
+ }
730
1040
  return orderBy;
731
1041
  }
732
1042
  normalizeFields(fields, prefix = '') {
@@ -750,7 +1060,7 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
750
1060
  }
751
1061
  if (prop.kind === core_1.ReferenceKind.EMBEDDED) {
752
1062
  if (prop.object) {
753
- ret.push(prop.fieldNames[0]);
1063
+ ret.push(prop.name);
754
1064
  return;
755
1065
  }
756
1066
  const parts = field.split('.');
@@ -762,20 +1072,17 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
762
1072
  }
763
1073
  return;
764
1074
  }
765
- if (prop.fieldNames) {
766
- ret.push(...prop.fieldNames);
767
- }
1075
+ ret.push(prop.name);
768
1076
  }
769
- buildFields(meta, populate, joinedProps, qb, fields) {
1077
+ buildFields(meta, populate, joinedProps, qb, alias, fields, options) {
770
1078
  const lazyProps = meta.props.filter(prop => prop.lazy && !populate.some(p => p.field === prop.name || p.all));
771
1079
  const hasLazyFormulas = meta.props.some(p => p.lazy && p.formula);
772
- const requiresSQLConversion = meta.props.some(p => p.customType?.convertToJSValueSQL);
1080
+ const requiresSQLConversion = meta.props.some(p => p.customType?.convertToJSValueSQL && p.persist !== false);
773
1081
  const hasExplicitFields = !!fields;
774
1082
  const ret = [];
775
- if (joinedProps.length > 0) {
776
- ret.push(...this.getFieldsForJoinedLoad(qb, meta, fields, populate));
777
- }
778
- else if (fields) {
1083
+ let addFormulas = false;
1084
+ // handle root entity properties first, this is used for both strategies in the same way
1085
+ if (fields) {
779
1086
  for (const field of this.normalizeFields(fields)) {
780
1087
  if (field === '*') {
781
1088
  ret.push('*');
@@ -792,28 +1099,39 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
792
1099
  });
793
1100
  this.processField(meta, prop, parts.join('.'), ret, populate, joinedProps, qb);
794
1101
  }
795
- ret.unshift(...meta.primaryKeys.filter(pk => !fields.includes(pk)));
1102
+ if (!fields.includes('*') && !fields.includes(`${qb.alias}.*`)) {
1103
+ ret.unshift(...meta.primaryKeys.filter(pk => !fields.includes(pk)));
1104
+ }
796
1105
  }
797
1106
  else if (lazyProps.filter(p => !p.formula).length > 0) {
798
1107
  const props = meta.props.filter(prop => this.platform.shouldHaveColumn(prop, populate, false));
799
1108
  ret.push(...core_1.Utils.flatten(props.filter(p => !lazyProps.includes(p)).map(p => p.fieldNames)));
1109
+ addFormulas = true;
800
1110
  }
801
1111
  else if (hasLazyFormulas || requiresSQLConversion) {
802
1112
  ret.push('*');
1113
+ addFormulas = true;
803
1114
  }
804
- if (ret.length > 0 && !hasExplicitFields) {
1115
+ else {
1116
+ ret.push('*');
1117
+ }
1118
+ if (ret.length > 0 && !hasExplicitFields && addFormulas) {
805
1119
  meta.props
806
1120
  .filter(prop => prop.formula && !lazyProps.includes(prop))
807
1121
  .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}`);
1122
+ const a = this.connection.getKnex().ref(alias).toString();
1123
+ const aliased = this.connection.getKnex().ref(prop.fieldNames[0]).toString();
1124
+ ret.push((0, core_1.raw)(`${prop.formula(a)} as ${aliased}`));
811
1125
  });
812
1126
  meta.props
813
- .filter(prop => prop.customType?.convertToDatabaseValueSQL || prop.customType?.convertToJSValueSQL)
1127
+ .filter(prop => prop.hasConvertToDatabaseValueSQL || prop.hasConvertToJSValueSQL)
814
1128
  .forEach(prop => ret.push(prop.name));
815
1129
  }
816
- return ret.length > 0 ? core_1.Utils.unique(ret) : ['*'];
1130
+ // add joined relations after the root entity fields
1131
+ if (joinedProps.length > 0) {
1132
+ ret.push(...this.getFieldsForJoinedLoad(qb, meta, fields, populate, options, alias));
1133
+ }
1134
+ return core_1.Utils.unique(ret);
817
1135
  }
818
1136
  }
819
1137
  exports.AbstractSqlDriver = AbstractSqlDriver;