@mikro-orm/knex 6.0.0-dev.31 → 6.0.0-dev.311

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