@mikro-orm/knex 6.0.0-dev.28 → 6.0.0-dev.280

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