@mikro-orm/knex 6.0.0-dev.29 → 6.0.0-dev.290

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