@mikro-orm/knex 7.0.0-dev.8 → 7.0.0-dev.80

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 (55) hide show
  1. package/AbstractSqlConnection.d.ts +11 -5
  2. package/AbstractSqlConnection.js +78 -32
  3. package/AbstractSqlDriver.d.ts +9 -5
  4. package/AbstractSqlDriver.js +274 -226
  5. package/AbstractSqlPlatform.js +5 -5
  6. package/PivotCollectionPersister.d.ts +3 -2
  7. package/PivotCollectionPersister.js +12 -21
  8. package/README.md +3 -2
  9. package/SqlEntityManager.d.ts +9 -2
  10. package/SqlEntityManager.js +2 -2
  11. package/dialects/mssql/MsSqlNativeQueryBuilder.d.ts +2 -0
  12. package/dialects/mssql/MsSqlNativeQueryBuilder.js +44 -3
  13. package/dialects/mysql/MySqlExceptionConverter.d.ts +3 -3
  14. package/dialects/mysql/MySqlExceptionConverter.js +4 -5
  15. package/dialects/mysql/MySqlSchemaHelper.js +2 -2
  16. package/dialects/postgresql/PostgreSqlTableCompiler.d.ts +1 -0
  17. package/dialects/postgresql/PostgreSqlTableCompiler.js +1 -0
  18. package/dialects/sqlite/BaseSqliteConnection.d.ts +3 -2
  19. package/dialects/sqlite/BaseSqliteConnection.js +2 -8
  20. package/dialects/sqlite/BaseSqlitePlatform.js +1 -2
  21. package/dialects/sqlite/SqliteExceptionConverter.d.ts +2 -2
  22. package/dialects/sqlite/SqliteExceptionConverter.js +6 -4
  23. package/dialects/sqlite/SqliteSchemaHelper.js +5 -6
  24. package/index.d.ts +1 -1
  25. package/index.js +1 -1
  26. package/package.json +5 -5
  27. package/query/ArrayCriteriaNode.d.ts +1 -0
  28. package/query/ArrayCriteriaNode.js +3 -0
  29. package/query/CriteriaNode.d.ts +4 -2
  30. package/query/CriteriaNode.js +11 -6
  31. package/query/CriteriaNodeFactory.js +12 -7
  32. package/query/NativeQueryBuilder.js +1 -1
  33. package/query/ObjectCriteriaNode.d.ts +1 -0
  34. package/query/ObjectCriteriaNode.js +39 -10
  35. package/query/QueryBuilder.d.ts +59 -7
  36. package/query/QueryBuilder.js +177 -53
  37. package/query/QueryBuilderHelper.d.ts +1 -1
  38. package/query/QueryBuilderHelper.js +18 -11
  39. package/query/ScalarCriteriaNode.d.ts +3 -3
  40. package/query/ScalarCriteriaNode.js +9 -7
  41. package/query/index.d.ts +1 -0
  42. package/query/index.js +1 -0
  43. package/query/raw.d.ts +59 -0
  44. package/query/raw.js +68 -0
  45. package/query/rawKnex.d.ts +58 -0
  46. package/query/rawKnex.js +72 -0
  47. package/schema/DatabaseSchema.js +25 -4
  48. package/schema/DatabaseTable.d.ts +5 -4
  49. package/schema/DatabaseTable.js +68 -34
  50. package/schema/SchemaComparator.js +4 -4
  51. package/schema/SchemaHelper.d.ts +2 -0
  52. package/schema/SchemaHelper.js +14 -10
  53. package/schema/SqlSchemaGenerator.d.ts +13 -6
  54. package/schema/SqlSchemaGenerator.js +40 -19
  55. package/typings.d.ts +85 -3
@@ -1,4 +1,4 @@
1
- import { ALIAS_REPLACEMENT_RE, DatabaseDriver, EntityManagerType, getOnConflictFields, getOnConflictReturningFields, helper, isRaw, LoadStrategy, parseJsonSafe, QueryFlag, QueryHelper, QueryOrder, raw, RawQueryFragment, ReferenceKind, Utils, } from '@mikro-orm/core';
1
+ import { ALIAS_REPLACEMENT_RE, DatabaseDriver, EntityManagerType, getLoadingStrategy, getOnConflictFields, getOnConflictReturningFields, helper, isRaw, LoadStrategy, parseJsonSafe, QueryFlag, QueryHelper, QueryOrder, raw, RawQueryFragment, ReferenceKind, Utils, } from '@mikro-orm/core';
2
2
  import { QueryBuilder } from './query/QueryBuilder.js';
3
3
  import { JoinType, QueryType } from './query/enums.js';
4
4
  import { SqlEntityManager } from './SqlEntityManager.js';
@@ -21,15 +21,11 @@ export class AbstractSqlDriver extends DatabaseDriver {
21
21
  const EntityManagerClass = this.config.get('entityManager', SqlEntityManager);
22
22
  return new EntityManagerClass(this.config, this, this.metadata, useContext);
23
23
  }
24
- async find(entityName, where, options = {}) {
25
- options = { populate: [], orderBy: [], ...options };
26
- const meta = this.metadata.find(entityName);
27
- if (meta?.virtual) {
28
- return this.findVirtual(entityName, where, options);
29
- }
24
+ async createQueryBuilderFromOptions(meta, where, options = {}) {
25
+ const connectionType = this.resolveConnectionType({ ctx: options.ctx, connectionType: options.connectionType });
30
26
  const populate = this.autoJoinOneToOneOwner(meta, options.populate, options.fields);
31
27
  const joinedProps = this.joinedProps(meta, populate, options);
32
- const qb = this.createQueryBuilder(entityName, options.ctx, options.connectionType, false, options.logging);
28
+ const qb = this.createQueryBuilder(meta.className, options.ctx, connectionType, false, options.logging, undefined, options.em);
33
29
  const fields = this.buildFields(meta, populate, joinedProps, qb, qb.alias, options);
34
30
  const orderBy = this.buildOrderBy(qb, meta, populate, options);
35
31
  const populateWhere = this.buildPopulateWhere(meta, joinedProps, options);
@@ -66,8 +62,17 @@ export class AbstractSqlDriver extends DatabaseDriver {
66
62
  if (options.em) {
67
63
  await qb.applyJoinedFilters(options.em, options.filters);
68
64
  }
65
+ return qb;
66
+ }
67
+ async find(entityName, where, options = {}) {
68
+ options = { populate: [], orderBy: [], ...options };
69
+ const meta = this.metadata.find(entityName);
70
+ if (meta?.virtual) {
71
+ return this.findVirtual(entityName, where, options);
72
+ }
73
+ const qb = await this.createQueryBuilderFromOptions(meta, where, options);
69
74
  const result = await this.rethrow(qb.execute('all'));
70
- if (isCursorPagination && !first && !!last) {
75
+ if (options.last && !options.first) {
71
76
  result.reverse();
72
77
  }
73
78
  return result;
@@ -107,7 +112,7 @@ export class AbstractSqlDriver extends DatabaseDriver {
107
112
  }
108
113
  async findFromVirtual(entityName, where, options, type) {
109
114
  const meta = this.metadata.get(entityName);
110
- /* v8 ignore next 3 */
115
+ /* v8 ignore next */
111
116
  if (!meta.expression) {
112
117
  return type === QueryType.SELECT ? [] : 0;
113
118
  }
@@ -130,32 +135,46 @@ export class AbstractSqlDriver extends DatabaseDriver {
130
135
  /* v8 ignore next */
131
136
  return res;
132
137
  }
133
- async wrapVirtualExpressionInSubquery(meta, expression, where, options, type) {
134
- const qb = this.createQueryBuilder(meta.className, options?.ctx, options.connectionType, options.convertCustomTypes, options.logging)
135
- .indexHint(options.indexHint)
136
- .comment(options.comments)
137
- .hintComment(options.hintComments);
138
- qb.where(where);
139
- const { first, last, before, after } = options;
140
- const isCursorPagination = [first, last, before, after].some(v => v != null);
141
- if (type !== QueryType.COUNT) {
142
- if (options.orderBy) {
143
- if (isCursorPagination) {
144
- const { orderBy: newOrderBy, where } = this.processCursorOptions(meta, options, options.orderBy);
145
- qb.andWhere(where).orderBy(newOrderBy);
146
- }
147
- else {
148
- qb.orderBy(options.orderBy);
149
- }
150
- }
151
- qb.limit(options?.limit, options?.offset);
138
+ async *streamFromVirtual(entityName, where, options) {
139
+ const meta = this.metadata.get(entityName);
140
+ /* v8 ignore next */
141
+ if (!meta.expression) {
142
+ return;
152
143
  }
153
- const native = qb.getNativeQuery(false).clear('select');
154
- if (type === QueryType.COUNT) {
155
- native.count();
144
+ if (typeof meta.expression === 'string') {
145
+ yield* this.wrapVirtualExpressionInSubqueryStream(meta, meta.expression, where, options, QueryType.SELECT);
146
+ return;
147
+ }
148
+ const em = this.createEntityManager();
149
+ em.setTransactionContext(options.ctx);
150
+ const res = meta.expression(em, where, options, true);
151
+ if (typeof res === 'string') {
152
+ yield* this.wrapVirtualExpressionInSubqueryStream(meta, res, where, options, QueryType.SELECT);
153
+ return;
154
+ }
155
+ if (res instanceof QueryBuilder) {
156
+ yield* this.wrapVirtualExpressionInSubqueryStream(meta, res.getFormattedQuery(), where, options, QueryType.SELECT);
157
+ return;
158
+ }
159
+ if (res instanceof RawQueryFragment) {
160
+ const expr = this.platform.formatQuery(res.sql, res.params);
161
+ yield* this.wrapVirtualExpressionInSubqueryStream(meta, expr, where, options, QueryType.SELECT);
162
+ return;
156
163
  }
157
- else { // select
158
- native.select('*');
164
+ /* v8 ignore next */
165
+ yield* res;
166
+ }
167
+ async wrapVirtualExpressionInSubquery(meta, expression, where, options, type) {
168
+ const qb = await this.createQueryBuilderFromOptions(meta, where, options);
169
+ qb.setFlag(QueryFlag.DISABLE_PAGINATE);
170
+ const isCursorPagination = [options.first, options.last, options.before, options.after].some(v => v != null);
171
+ const native = qb.getNativeQuery(false);
172
+ if (type === QueryType.COUNT) {
173
+ native
174
+ .clear('select')
175
+ .clear('limit')
176
+ .clear('offset')
177
+ .count();
159
178
  }
160
179
  native.from(raw(`(${expression}) as ${this.platform.quoteIdentifier(qb.alias)}`));
161
180
  const query = native.compile();
@@ -163,14 +182,26 @@ export class AbstractSqlDriver extends DatabaseDriver {
163
182
  if (type === QueryType.COUNT) {
164
183
  return res[0].count;
165
184
  }
166
- if (isCursorPagination && !first && !!last) {
185
+ if (isCursorPagination && !options.first && !!options.last) {
167
186
  res.reverse();
168
187
  }
169
188
  return res.map(row => this.mapResult(row, meta));
170
189
  }
190
+ async *wrapVirtualExpressionInSubqueryStream(meta, expression, where, options, type) {
191
+ const qb = await this.createQueryBuilderFromOptions(meta, where, options);
192
+ qb.unsetFlag(QueryFlag.DISABLE_PAGINATE);
193
+ const native = qb.getNativeQuery(false);
194
+ native.from(raw(`(${expression}) as ${this.platform.quoteIdentifier(qb.alias)}`));
195
+ const query = native.compile();
196
+ const connectionType = this.resolveConnectionType({ ctx: options.ctx, connectionType: options.connectionType });
197
+ const res = this.getConnection(connectionType).stream(query.sql, query.params, options.ctx, options.loggerContext);
198
+ for await (const row of res) {
199
+ yield this.mapResult(row, meta);
200
+ }
201
+ }
171
202
  mapResult(result, meta, populate = [], qb, map = {}) {
172
203
  const ret = super.mapResult(result, meta);
173
- /* v8 ignore next 3 */
204
+ /* v8 ignore next */
174
205
  if (!ret) {
175
206
  return null;
176
207
  }
@@ -185,7 +216,7 @@ export class AbstractSqlDriver extends DatabaseDriver {
185
216
  joinedProps.forEach(hint => {
186
217
  const [propName, ref] = hint.field.split(':', 2);
187
218
  const prop = meta.properties[propName];
188
- /* v8 ignore next 3 */
219
+ /* v8 ignore next */
189
220
  if (!prop) {
190
221
  return;
191
222
  }
@@ -199,7 +230,7 @@ export class AbstractSqlDriver extends DatabaseDriver {
199
230
  path += '[pivot]';
200
231
  }
201
232
  const relationAlias = qb.getAliasForJoinPath(path, { matchPopulateJoins: true });
202
- /* v8 ignore next 3 */
233
+ /* v8 ignore next */
203
234
  if (!relationAlias) {
204
235
  return;
205
236
  }
@@ -221,9 +252,13 @@ export class AbstractSqlDriver extends DatabaseDriver {
221
252
  }
222
253
  return;
223
254
  }
255
+ const mapToPk = !!(ref || prop.mapToPk);
256
+ const targetProps = mapToPk
257
+ ? meta2.getPrimaryProps()
258
+ : meta2.props.filter(prop => this.platform.shouldHaveColumn(prop, hint.children || []));
224
259
  // If the primary key value for the relation is null, we know we haven't joined to anything
225
260
  // and therefore we don't return any record (since all values would be null)
226
- const hasPK = meta2.primaryKeys.every(pk => meta2.properties[pk].fieldNames.every(name => {
261
+ const hasPK = meta2.getPrimaryProps().every(pk => pk.fieldNames.every(name => {
227
262
  return root[`${relationAlias}__${name}`] != null;
228
263
  }));
229
264
  if (!hasPK) {
@@ -233,14 +268,18 @@ export class AbstractSqlDriver extends DatabaseDriver {
233
268
  if ([ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind)) {
234
269
  result[prop.name] = null;
235
270
  }
271
+ for (const prop of targetProps) {
272
+ for (const name of prop.fieldNames) {
273
+ delete root[`${relationAlias}__${name}`];
274
+ }
275
+ }
236
276
  return;
237
277
  }
238
278
  let relationPojo = {};
239
279
  meta2.props
240
280
  .filter(prop => !ref && prop.persist === false && prop.fieldNames)
241
- .filter(prop => !prop.lazy || populate.some(p => p.field === prop.name || p.all))
242
281
  .forEach(prop => {
243
- /* v8 ignore next 3 */
282
+ /* v8 ignore next */
244
283
  if (prop.fieldNames.length > 1) { // composite keys
245
284
  relationPojo[prop.name] = prop.fieldNames.map(name => root[`${relationAlias}__${name}`]);
246
285
  }
@@ -249,10 +288,6 @@ export class AbstractSqlDriver extends DatabaseDriver {
249
288
  relationPojo[prop.name] = root[alias];
250
289
  }
251
290
  });
252
- const mapToPk = !!(ref || prop.mapToPk);
253
- const targetProps = mapToPk
254
- ? meta2.getPrimaryProps()
255
- : meta2.props.filter(prop => this.platform.shouldHaveColumn(prop, hint.children || []));
256
291
  const tz = this.platform.getTimezone();
257
292
  for (const prop of targetProps) {
258
293
  if (prop.fieldNames.every(name => typeof root[`${relationAlias}__${name}`] === 'undefined')) {
@@ -293,7 +328,9 @@ export class AbstractSqlDriver extends DatabaseDriver {
293
328
  // properties can be mapped to multiple places, e.g. when sharing a column in multiple FKs,
294
329
  // so we need to delete them after everything is mapped from given level
295
330
  for (const prop of targetProps) {
296
- prop.fieldNames.map(name => delete root[`${relationAlias}__${name}`]);
331
+ for (const name of prop.fieldNames) {
332
+ delete root[`${relationAlias}__${name}`];
333
+ }
297
334
  }
298
335
  if (mapToPk) {
299
336
  const tmp = Object.values(relationPojo);
@@ -312,15 +349,20 @@ export class AbstractSqlDriver extends DatabaseDriver {
312
349
  });
313
350
  }
314
351
  async count(entityName, where, options = {}) {
315
- const meta = this.metadata.find(entityName);
316
- if (meta?.virtual) {
352
+ const meta = this.metadata.get(entityName);
353
+ if (meta.virtual) {
317
354
  return this.countVirtual(entityName, where, options);
318
355
  }
319
- const joinedProps = meta ? this.joinedProps(meta, options.populate ?? []) : [];
320
- const populateWhere = meta ? this.buildPopulateWhere(meta, joinedProps, options) : undefined;
321
- const populate = options.populate ?? [];
322
- const qb = this.createQueryBuilder(entityName, options.ctx, options.connectionType, false, options.logging)
323
- .indexHint(options.indexHint)
356
+ options = { populate: [], ...options };
357
+ const populate = options.populate;
358
+ const joinedProps = this.joinedProps(meta, populate, options);
359
+ const qb = this.createQueryBuilder(entityName, options.ctx, options.connectionType, false, options.logging);
360
+ const populateWhere = this.buildPopulateWhere(meta, joinedProps, options);
361
+ if (meta && !Utils.isEmpty(populate)) {
362
+ this.buildFields(meta, populate, joinedProps, qb, qb.alias, options);
363
+ }
364
+ qb.__populateWhere = options._populateWhere;
365
+ qb.indexHint(options.indexHint)
324
366
  .comment(options.comments)
325
367
  .hintComment(options.hintComments)
326
368
  .groupBy(options.groupBy)
@@ -328,8 +370,8 @@ export class AbstractSqlDriver extends DatabaseDriver {
328
370
  .populate(populate, joinedProps.length > 0 ? populateWhere : undefined, joinedProps.length > 0 ? options.populateFilter : undefined)
329
371
  .withSchema(this.getSchemaName(meta, options))
330
372
  .where(where);
331
- if (meta && !Utils.isEmpty(populate)) {
332
- this.buildFields(meta, populate, joinedProps, qb, qb.alias, options, true);
373
+ if (options.em) {
374
+ await qb.applyJoinedFilters(options.em, options.filters);
333
375
  }
334
376
  return this.rethrow(qb.getCount());
335
377
  }
@@ -338,7 +380,7 @@ export class AbstractSqlDriver extends DatabaseDriver {
338
380
  const meta = this.metadata.find(entityName);
339
381
  const collections = this.extractManyToMany(entityName, data);
340
382
  const pks = meta?.primaryKeys ?? [this.config.getNamingStrategy().referenceColumnName()];
341
- const qb = this.createQueryBuilder(entityName, options.ctx, 'write', options.convertCustomTypes).withSchema(this.getSchemaName(meta, options));
383
+ const qb = this.createQueryBuilder(entityName, options.ctx, 'write', options.convertCustomTypes, options.loggerContext).withSchema(this.getSchemaName(meta, options));
342
384
  const res = await this.rethrow(qb.insert(data).execute('run', false));
343
385
  res.row = res.row || {};
344
386
  let pk;
@@ -386,7 +428,12 @@ export class AbstractSqlDriver extends DatabaseDriver {
386
428
  sql += ' ' + data.map(() => `select null as ${this.platform.quoteIdentifier(pks[0])}`).join(' union all ');
387
429
  }
388
430
  const addParams = (prop, row) => {
389
- let value = row[prop.name] ?? prop.default;
431
+ const rowValue = row[prop.name];
432
+ if (prop.nullable && rowValue === null) {
433
+ params.push(null);
434
+ return;
435
+ }
436
+ let value = rowValue ?? prop.default;
390
437
  if (prop.kind === ReferenceKind.EMBEDDED && prop.object) {
391
438
  if (prop.array && value) {
392
439
  value = this.platform.cloneEmbeddable(value);
@@ -399,14 +446,14 @@ export class AbstractSqlDriver extends DatabaseDriver {
399
446
  value = this.mapDataToFieldNames(value, false, prop.embeddedProps, options.convertCustomTypes);
400
447
  }
401
448
  }
402
- if (options.convertCustomTypes && prop.customType) {
403
- params.push(prop.customType.convertToDatabaseValue(value, this.platform, { key: prop.name, mode: 'query-data' }));
404
- return;
405
- }
406
449
  if (typeof value === 'undefined' && this.platform.usesDefaultKeyword()) {
407
450
  params.push(raw('default'));
408
451
  return;
409
452
  }
453
+ if (options.convertCustomTypes && prop.customType) {
454
+ params.push(prop.customType.convertToDatabaseValue(value, this.platform, { key: prop.name, mode: 'query-data' }));
455
+ return;
456
+ }
410
457
  params.push(value);
411
458
  };
412
459
  if (fields.length > 0 || this.platform.usesDefaultKeyword()) {
@@ -465,9 +512,9 @@ export class AbstractSqlDriver extends DatabaseDriver {
465
512
  if (transform) {
466
513
  sql = transform(sql);
467
514
  }
468
- const res = await this.execute(sql, params, 'run', options.ctx);
515
+ const res = await this.execute(sql, params, 'run', options.ctx, options.loggerContext);
469
516
  let pk;
470
- /* v8 ignore next 3 */
517
+ /* v8 ignore next */
471
518
  if (pks.length > 1) { // owner has composite pk
472
519
  pk = data.map(d => Utils.getPrimaryKeyCond(d, pks));
473
520
  }
@@ -493,7 +540,7 @@ export class AbstractSqlDriver extends DatabaseDriver {
493
540
  where = { [meta?.primaryKeys[0] ?? pks[0]]: where };
494
541
  }
495
542
  if (Utils.hasObjectKeys(data)) {
496
- const qb = this.createQueryBuilder(entityName, options.ctx, 'write', options.convertCustomTypes)
543
+ const qb = this.createQueryBuilder(entityName, options.ctx, 'write', options.convertCustomTypes, options.loggerContext)
497
544
  .withSchema(this.getSchemaName(meta, options));
498
545
  if (options.upsert) {
499
546
  /* v8 ignore next */
@@ -532,7 +579,7 @@ export class AbstractSqlDriver extends DatabaseDriver {
532
579
  const meta = this.metadata.get(entityName);
533
580
  if (options.upsert) {
534
581
  const uniqueFields = options.onConflictFields ?? (Utils.isPlainObject(where[0]) ? Object.keys(where[0]).flatMap(key => Utils.splitPrimaryKeys(key)) : meta.primaryKeys);
535
- const qb = this.createQueryBuilder(entityName, options.ctx, 'write', options.convertCustomTypes).withSchema(this.getSchemaName(meta, options));
582
+ const qb = this.createQueryBuilder(entityName, options.ctx, 'write', options.convertCustomTypes, options.loggerContext).withSchema(this.getSchemaName(meta, options));
536
583
  const returning = getOnConflictReturningFields(meta, data[0], uniqueFields, options);
537
584
  qb.insert(data)
538
585
  .onConflict(uniqueFields)
@@ -645,7 +692,7 @@ export class AbstractSqlDriver extends DatabaseDriver {
645
692
  /* v8 ignore next */
646
693
  sql += returningFields.length > 0 ? ` returning ${returningFields.map(field => this.platform.quoteIdentifier(field)).join(', ')}` : '';
647
694
  }
648
- const res = await this.rethrow(this.execute(sql, params, 'run', options.ctx));
695
+ const res = await this.rethrow(this.execute(sql, params, 'run', options.ctx, options.loggerContext));
649
696
  for (let i = 0; i < collections.length; i++) {
650
697
  await this.processManyToMany(meta, where[i], collections[i], false, options);
651
698
  }
@@ -657,7 +704,7 @@ export class AbstractSqlDriver extends DatabaseDriver {
657
704
  if (Utils.isPrimaryKey(where) && pks.length === 1) {
658
705
  where = { [pks[0]]: where };
659
706
  }
660
- const qb = this.createQueryBuilder(entityName, options.ctx, 'write', false).delete(where).withSchema(this.getSchemaName(meta, options));
707
+ const qb = this.createQueryBuilder(entityName, options.ctx, 'write', false, options.loggerContext).delete(where).withSchema(this.getSchemaName(meta, options));
661
708
  return this.rethrow(qb.execute('run', false));
662
709
  }
663
710
  /**
@@ -721,7 +768,7 @@ export class AbstractSqlDriver extends DatabaseDriver {
721
768
  await this.rethrow(query.execute());
722
769
  continue;
723
770
  }
724
- /* v8 ignore next 5 */
771
+ /* v8 ignore next */
725
772
  const query = qb.update({ [coll.property.mappedBy]: pks })
726
773
  .where({ [cols.join(Utils.PK_SEPARATOR)]: { $in: insertDiff } });
727
774
  await this.rethrow(query.execute());
@@ -731,14 +778,20 @@ export class AbstractSqlDriver extends DatabaseDriver {
731
778
  const pivotMeta = this.metadata.find(coll.property.pivotEntity);
732
779
  let schema = pivotMeta.schema;
733
780
  if (schema === '*') {
734
- const ownerSchema = wrapped.getSchema() === '*' ? this.config.get('schema') : wrapped.getSchema();
735
- schema = coll.property.owner ? ownerSchema : this.config.get('schema');
781
+ if (coll.property.owner) {
782
+ schema = wrapped.getSchema() === '*' ? options?.schema ?? this.config.get('schema') : wrapped.getSchema();
783
+ }
784
+ else {
785
+ const targetMeta = coll.property.targetMeta;
786
+ const targetSchema = (coll[0] ?? snap?.[0]) && helper(coll[0] ?? snap?.[0]).getSchema();
787
+ schema = targetMeta.schema === '*' ? options?.schema ?? targetSchema ?? this.config.get('schema') : targetMeta.schema;
788
+ }
736
789
  }
737
790
  else if (schema == null) {
738
791
  schema = this.config.get('schema');
739
792
  }
740
793
  const tableName = `${schema ?? '_'}.${pivotMeta.tableName}`;
741
- const persister = groups[tableName] ??= new PivotCollectionPersister(pivotMeta, this, options?.ctx, schema);
794
+ const persister = groups[tableName] ??= new PivotCollectionPersister(pivotMeta, this, options?.ctx, schema, options?.loggerContext);
742
795
  persister.enqueueUpdate(coll.property, insertDiff, deleteDiff, pks);
743
796
  }
744
797
  for (const persister of Utils.values(groups)) {
@@ -746,116 +799,88 @@ export class AbstractSqlDriver extends DatabaseDriver {
746
799
  }
747
800
  }
748
801
  async loadFromPivotTable(prop, owners, where = {}, orderBy, ctx, options, pivotJoin) {
802
+ if (owners.length === 0) {
803
+ return {};
804
+ }
749
805
  const pivotMeta = this.metadata.find(prop.pivotEntity);
750
806
  const pivotProp1 = pivotMeta.relations[prop.owner ? 1 : 0];
751
807
  const pivotProp2 = pivotMeta.relations[prop.owner ? 0 : 1];
752
808
  const ownerMeta = this.metadata.find(pivotProp2.type);
753
- options = { ...options };
754
- const qb = this.createQueryBuilder(prop.pivotEntity, ctx, options.connectionType, undefined, options?.logging)
755
- .withSchema(this.getSchemaName(pivotMeta, options))
756
- .indexHint(options.indexHint)
757
- .comment(options.comments)
758
- .hintComment(options.hintComments);
759
- const pivotAlias = qb.alias;
760
- const pivotKey = pivotProp2.joinColumns.map(column => `${pivotAlias}.${column}`).join(Utils.PK_SEPARATOR);
761
809
  const cond = {
762
- [pivotKey]: { $in: ownerMeta.compositePK ? owners : owners.map(o => o[0]) },
810
+ [pivotProp2.name]: { $in: ownerMeta.compositePK ? owners : owners.map(o => o[0]) },
763
811
  };
764
- /* v8 ignore next 3 */
765
- if (!Utils.isEmpty(where) && Object.keys(where).every(k => Utils.isOperator(k, false))) {
766
- where = cond;
767
- }
768
- else {
769
- where = { ...where, ...cond };
770
- }
771
- orderBy = this.getPivotOrderBy(prop, pivotProp1, pivotAlias, orderBy);
772
- const populate = this.autoJoinOneToOneOwner(prop.targetMeta, []);
773
- const fields = [];
774
- const k1 = !prop.owner ? 'joinColumns' : 'inverseJoinColumns';
775
- const k2 = prop.owner ? 'joinColumns' : 'inverseJoinColumns';
776
- const cols = [
777
- ...prop[k1].map(col => `${pivotAlias}.${col} as fk__${col}`),
778
- ...prop[k2].map(col => `${pivotAlias}.${col} as fk__${col}`),
779
- ];
780
- fields.push(...cols);
781
- if (!pivotJoin) {
782
- const targetAlias = qb.getNextAlias(prop.targetMeta.tableName);
783
- const targetSchema = this.getSchemaName(prop.targetMeta, options) ?? this.platform.getDefaultSchemaName();
784
- qb.innerJoin(pivotProp1.name, targetAlias, {}, targetSchema);
785
- const targetFields = this.buildFields(prop.targetMeta, (options.populate ?? []), [], qb, targetAlias, options);
786
- for (const field of targetFields) {
787
- const f = field.toString();
788
- fields.unshift(f.includes('.') ? field : `${targetAlias}.${f}`);
789
- if (RawQueryFragment.isKnownFragment(field)) {
790
- qb.rawFragments.add(f);
791
- }
792
- }
793
- // we need to handle 1:1 owner auto-joins explicitly, as the QB type is the pivot table, not the target
794
- populate.forEach(hint => {
795
- const alias = qb.getNextAlias(prop.targetMeta.tableName);
796
- qb.leftJoin(`${targetAlias}.${hint.field}`, alias);
797
- // eslint-disable-next-line dot-notation
798
- for (const join of Object.values(qb['_joins'])) {
799
- const [propName] = hint.field.split(':', 2);
800
- if (join.alias === alias && join.prop.name === propName) {
801
- fields.push(...qb.helper.mapJoinColumns(qb.type, join));
802
- }
803
- }
804
- });
805
- }
806
- qb.select(fields)
807
- .where({ [pivotProp1.name]: where })
808
- .orderBy(orderBy)
809
- .setLockMode(options.lockMode, options.lockTableAliases);
810
- if (owners.length === 1 && (options.offset != null || options.limit != null)) {
811
- qb.limit(options.limit, options.offset);
812
- }
813
- const res = owners.length ? await this.rethrow(qb.execute('all', { mergeResults: false, mapResults: false })) : [];
814
- const tmp = {};
815
- const items = res.map((row) => {
816
- const root = super.mapResult(row, prop.targetMeta);
817
- this.mapJoinedProps(root, prop.targetMeta, populate, qb, root, tmp, pivotMeta.className + '.' + pivotProp1.name);
818
- return root;
812
+ if (!Utils.isEmpty(where)) {
813
+ cond[pivotProp1.name] = { ...where };
814
+ }
815
+ where = cond;
816
+ const populateField = pivotJoin ? `${pivotProp1.name}:ref` : pivotProp1.name;
817
+ const populate = this.autoJoinOneToOneOwner(prop.targetMeta, options?.populate ?? [], options?.fields);
818
+ const childFields = !Utils.isEmpty(options?.fields) ? options.fields.map(f => `${pivotProp1.name}.${f}`) : [];
819
+ const childExclude = !Utils.isEmpty(options?.exclude) ? options.exclude.map(f => `${pivotProp1.name}.${f}`) : [];
820
+ const fields = pivotJoin
821
+ ? [pivotProp1.name, pivotProp2.name]
822
+ : [pivotProp1.name, pivotProp2.name, ...childFields];
823
+ const res = await this.find(pivotMeta.className, where, {
824
+ ctx,
825
+ ...options,
826
+ fields,
827
+ exclude: childExclude,
828
+ orderBy: this.getPivotOrderBy(prop, pivotProp1, orderBy, options?.orderBy),
829
+ populate: [{ field: populateField, strategy: LoadStrategy.JOINED, joinType: JoinType.innerJoin, children: populate }],
830
+ populateWhere: undefined,
831
+ // @ts-ignore
832
+ _populateWhere: 'infer',
833
+ populateFilter: !Utils.isEmpty(options?.populateFilter) ? { [pivotProp2.name]: options?.populateFilter } : undefined,
819
834
  });
820
- qb.clearRawFragmentsCache();
821
835
  const map = {};
822
- const pkProps = ownerMeta.getPrimaryProps();
823
836
  for (const owner of owners) {
824
- const key = Utils.getPrimaryKeyHash(prop.joinColumns.map((_col, idx) => {
825
- const pkProp = pkProps[idx];
826
- return pkProp.customType ? pkProp.customType.convertToJSValue(owner[idx], this.platform) : owner[idx];
827
- }));
837
+ const key = Utils.getPrimaryKeyHash(owner);
828
838
  map[key] = [];
829
839
  }
830
- for (const item of items) {
831
- const key = Utils.getPrimaryKeyHash(prop.joinColumns.map((col, idx) => {
832
- const pkProp = pkProps[idx];
833
- return pkProp.customType ? pkProp.customType.convertToJSValue(item[`fk__${col}`], this.platform) : item[`fk__${col}`];
834
- }));
835
- map[key].push(item);
836
- prop.joinColumns.forEach(col => delete item[`fk__${col}`]);
837
- prop.inverseJoinColumns.forEach((col, idx) => {
838
- Utils.renameKey(item, `fk__${col}`, prop.targetMeta.primaryKeys[idx]);
839
- });
840
+ for (const item of res) {
841
+ const key = Utils.getPrimaryKeyHash(Utils.asArray(item[pivotProp2.name]));
842
+ map[key].push(item[pivotProp1.name]);
840
843
  }
841
844
  return map;
842
845
  }
843
- getPivotOrderBy(prop, pivotProp, pivotAlias, orderBy) {
844
- // FIXME this is ignoring the rest of the array items
846
+ getPivotOrderBy(prop, pivotProp, orderBy, parentOrderBy) {
845
847
  if (!Utils.isEmpty(orderBy)) {
846
- return [{ [pivotProp.name]: Utils.asArray(orderBy)[0] }];
848
+ return Utils.asArray(orderBy).map(o => ({ [pivotProp.name]: o }));
849
+ }
850
+ if (prop.kind === ReferenceKind.MANY_TO_MANY && Utils.asArray(parentOrderBy).some(o => o[prop.name])) {
851
+ return Utils.asArray(parentOrderBy)
852
+ .filter(o => o[prop.name])
853
+ .map(o => ({ [pivotProp.name]: o[prop.name] }));
847
854
  }
848
855
  if (!Utils.isEmpty(prop.orderBy)) {
849
- return [{ [pivotProp.name]: Utils.asArray(prop.orderBy)[0] }];
856
+ return Utils.asArray(prop.orderBy).map(o => ({ [pivotProp.name]: o }));
850
857
  }
851
858
  if (prop.fixedOrder) {
852
- return [{ [`${pivotAlias}.${prop.fixedOrderColumn}`]: QueryOrder.ASC }];
859
+ return [{ [prop.fixedOrderColumn]: QueryOrder.ASC }];
853
860
  }
854
861
  return [];
855
862
  }
856
863
  async execute(query, params = [], method = 'all', ctx, loggerContext) {
857
864
  return this.rethrow(this.connection.execute(query, params, method, ctx, loggerContext));
858
865
  }
866
+ async *stream(entityName, where, options) {
867
+ options = { populate: [], orderBy: [], ...options };
868
+ const meta = this.metadata.find(entityName);
869
+ if (meta?.virtual) {
870
+ yield* this.streamFromVirtual(entityName, where, options);
871
+ return;
872
+ }
873
+ const qb = await this.createQueryBuilderFromOptions(meta, where, options);
874
+ try {
875
+ const result = qb.stream(options);
876
+ for await (const item of result) {
877
+ yield item;
878
+ }
879
+ }
880
+ catch (e) {
881
+ throw this.convertException(e);
882
+ }
883
+ }
859
884
  /**
860
885
  * 1:1 owner side needs to be marked for population so QB auto-joins the owner id
861
886
  */
@@ -867,7 +892,7 @@ export class AbstractSqlDriver extends DatabaseDriver {
867
892
  const toPopulate = meta.relations
868
893
  .filter(prop => prop.kind === ReferenceKind.ONE_TO_ONE && !prop.owner && !prop.lazy && !relationsToPopulate.includes(prop.name))
869
894
  .filter(prop => fields.length === 0 || fields.some(f => prop.name === f || prop.name.startsWith(`${String(f)}.`)))
870
- .map(prop => ({ field: `${prop.name}:ref`, strategy: prop.strategy }));
895
+ .map(prop => ({ field: `${prop.name}:ref`, strategy: LoadStrategy.JOINED }));
871
896
  return [...populate, ...toPopulate];
872
897
  }
873
898
  /**
@@ -877,14 +902,15 @@ export class AbstractSqlDriver extends DatabaseDriver {
877
902
  return populate.filter(hint => {
878
903
  const [propName, ref] = hint.field.split(':', 2);
879
904
  const prop = meta.properties[propName] || {};
880
- if (hint.filter && hint.strategy === LoadStrategy.JOINED) {
905
+ const strategy = getLoadingStrategy(hint.strategy || prop.strategy || options?.strategy || this.config.get('loadStrategy'), prop.kind);
906
+ if (ref && [ReferenceKind.ONE_TO_ONE, ReferenceKind.MANY_TO_ONE].includes(prop.kind)) {
881
907
  return true;
882
908
  }
883
909
  // skip redundant joins for 1:1 owner population hints when using `mapToPk`
884
910
  if (prop.kind === ReferenceKind.ONE_TO_ONE && prop.mapToPk && prop.owner) {
885
911
  return false;
886
912
  }
887
- if ((options?.strategy || hint.strategy || prop.strategy || this.config.get('loadStrategy')) !== LoadStrategy.JOINED) {
913
+ if (strategy !== LoadStrategy.JOINED) {
888
914
  // force joined strategy for explicit 1:1 owner populate hint as it would require a join anyway
889
915
  return prop.kind === ReferenceKind.ONE_TO_ONE && !prop.owner;
890
916
  }
@@ -895,31 +921,26 @@ export class AbstractSqlDriver extends DatabaseDriver {
895
921
  * @internal
896
922
  */
897
923
  mergeJoinedResult(rawResults, meta, joinedProps) {
924
+ if (rawResults.length <= 1) {
925
+ return rawResults;
926
+ }
898
927
  const res = [];
899
928
  const map = {};
929
+ const collectionsToMerge = {};
930
+ const hints = joinedProps.map(hint => {
931
+ const [propName, ref] = hint.field.split(':', 2);
932
+ return { propName, ref, children: hint.children };
933
+ });
900
934
  for (const item of rawResults) {
901
935
  const pk = Utils.getCompositeKeyHash(item, meta);
902
936
  if (map[pk]) {
903
- for (const hint of joinedProps) {
904
- const [propName, ref] = hint.field.split(':', 2);
905
- const prop = meta.properties[propName];
937
+ for (const { propName } of hints) {
906
938
  if (!item[propName]) {
907
939
  continue;
908
940
  }
909
- if ([ReferenceKind.ONE_TO_MANY, ReferenceKind.MANY_TO_MANY].includes(prop.kind) && ref) {
910
- map[pk][propName] = [...map[pk][propName], ...item[propName]];
911
- continue;
912
- }
913
- switch (prop.kind) {
914
- case ReferenceKind.ONE_TO_MANY:
915
- case ReferenceKind.MANY_TO_MANY:
916
- map[pk][propName] = this.mergeJoinedResult([...map[pk][propName], ...item[propName]], prop.targetMeta, hint.children ?? []);
917
- break;
918
- case ReferenceKind.MANY_TO_ONE:
919
- case ReferenceKind.ONE_TO_ONE:
920
- map[pk][propName] = this.mergeJoinedResult([map[pk][propName], item[propName]], prop.targetMeta, hint.children ?? [])[0];
921
- break;
922
- }
941
+ collectionsToMerge[pk] ??= {};
942
+ collectionsToMerge[pk][propName] ??= [map[pk][propName]];
943
+ collectionsToMerge[pk][propName].push(item[propName]);
923
944
  }
924
945
  }
925
946
  else {
@@ -927,37 +948,59 @@ export class AbstractSqlDriver extends DatabaseDriver {
927
948
  res.push(item);
928
949
  }
929
950
  }
951
+ for (const pk in collectionsToMerge) {
952
+ const entity = map[pk];
953
+ const collections = collectionsToMerge[pk];
954
+ for (const { propName, ref, children } of hints) {
955
+ if (!collections[propName]) {
956
+ continue;
957
+ }
958
+ const prop = meta.properties[propName];
959
+ const items = collections[propName].flat();
960
+ if ([ReferenceKind.ONE_TO_MANY, ReferenceKind.MANY_TO_MANY].includes(prop.kind) && ref) {
961
+ entity[propName] = items;
962
+ continue;
963
+ }
964
+ switch (prop.kind) {
965
+ case ReferenceKind.ONE_TO_MANY:
966
+ case ReferenceKind.MANY_TO_MANY:
967
+ entity[propName] = this.mergeJoinedResult(items, prop.targetMeta, children ?? []);
968
+ break;
969
+ case ReferenceKind.MANY_TO_ONE:
970
+ case ReferenceKind.ONE_TO_ONE:
971
+ entity[propName] = this.mergeJoinedResult(items, prop.targetMeta, children ?? [])[0];
972
+ break;
973
+ }
974
+ }
975
+ }
930
976
  return res;
931
977
  }
978
+ shouldHaveColumn(meta, prop, populate, fields, exclude) {
979
+ if (!this.platform.shouldHaveColumn(prop, populate, exclude)) {
980
+ return false;
981
+ }
982
+ if (!fields || fields.includes('*') || prop.primary || meta.root.discriminatorColumn === prop.name) {
983
+ return true;
984
+ }
985
+ return fields.some(f => f === prop.name || f.toString().startsWith(prop.name + '.'));
986
+ }
932
987
  getFieldsForJoinedLoad(qb, meta, options) {
933
988
  const fields = [];
934
989
  const populate = options.populate ?? [];
935
990
  const joinedProps = this.joinedProps(meta, populate, options);
936
- const shouldHaveColumn = (meta, prop, populate, fields) => {
937
- if (!this.platform.shouldHaveColumn(prop, populate, options.exclude)) {
938
- return false;
939
- }
940
- if (!fields || fields.includes('*') || prop.primary || meta.root.discriminatorColumn === prop.name) {
941
- return true;
942
- }
943
- return fields.some(f => f === prop.name || f.toString().startsWith(prop.name + '.'));
944
- };
945
991
  const populateWhereAll = options?._populateWhere === 'all' || Utils.isEmpty(options?._populateWhere);
946
992
  // root entity is already handled, skip that
947
993
  if (options.parentJoinPath) {
948
994
  // alias all fields in the primary table
949
995
  meta.props
950
- .filter(prop => shouldHaveColumn(meta, prop, populate, options.explicitFields))
951
- .forEach(prop => fields.push(...this.mapPropToFieldNames(qb, prop, options.parentTableAlias)));
996
+ .filter(prop => this.shouldHaveColumn(meta, prop, populate, options.explicitFields, options.exclude))
997
+ .forEach(prop => fields.push(...this.mapPropToFieldNames(qb, prop, options.parentTableAlias, options.explicitFields)));
952
998
  }
953
999
  for (const hint of joinedProps) {
954
1000
  const [propName, ref] = hint.field.split(':', 2);
955
1001
  const prop = meta.properties[propName];
956
1002
  // ignore ref joins of known FKs unless it's a filter hint
957
- if (ref && !hint.filter && (prop.kind === ReferenceKind.MANY_TO_ONE || (prop.kind === ReferenceKind.ONE_TO_ONE && !prop.owner))) {
958
- continue;
959
- }
960
- if (options.count && !options?.populateFilter?.[prop.name]) {
1003
+ if (ref && !hint.filter && (prop.kind === ReferenceKind.MANY_TO_ONE || (prop.kind === ReferenceKind.ONE_TO_ONE && prop.owner))) {
961
1004
  continue;
962
1005
  }
963
1006
  const meta2 = this.metadata.find(prop.type);
@@ -968,11 +1011,14 @@ export class AbstractSqlDriver extends DatabaseDriver {
968
1011
  if (!options.parentJoinPath && populateWhereAll && !hint.filter && !path.startsWith('[populate]')) {
969
1012
  path = '[populate]' + path;
970
1013
  }
1014
+ const mandatoryToOneProperty = [ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind) && !prop.nullable;
971
1015
  const joinType = pivotRefJoin
972
1016
  ? JoinType.pivotJoin
973
- : hint.filter && !prop.nullable
974
- ? JoinType.innerJoin
975
- : JoinType.leftJoin;
1017
+ : hint.joinType
1018
+ ? hint.joinType
1019
+ : (hint.filter && !prop.nullable) || mandatoryToOneProperty
1020
+ ? JoinType.innerJoin
1021
+ : JoinType.leftJoin;
976
1022
  qb.join(field, tableAlias, {}, joinType, path);
977
1023
  if (pivotRefJoin) {
978
1024
  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}`)));
@@ -1004,7 +1050,7 @@ export class AbstractSqlDriver extends DatabaseDriver {
1004
1050
  parentJoinPath: path,
1005
1051
  }));
1006
1052
  }
1007
- else if (hint.filter || prop.mapToPk) {
1053
+ else if (hint.filter || prop.mapToPk || (ref && [ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind))) {
1008
1054
  fields.push(...prop.referencedColumnNames.map(col => qb.helper.mapper(`${tableAlias}.${col}`, qb.type, undefined, `${tableAlias}__${col}`)));
1009
1055
  }
1010
1056
  }
@@ -1013,9 +1059,18 @@ export class AbstractSqlDriver extends DatabaseDriver {
1013
1059
  /**
1014
1060
  * @internal
1015
1061
  */
1016
- mapPropToFieldNames(qb, prop, tableAlias) {
1062
+ mapPropToFieldNames(qb, prop, tableAlias, explicitFields) {
1063
+ if (prop.kind === ReferenceKind.EMBEDDED && !prop.object) {
1064
+ return Object.entries(prop.embeddedProps).flatMap(([name, childProp]) => {
1065
+ const childFields = explicitFields ? Utils.extractChildElements(explicitFields, prop.name) : [];
1066
+ if (childFields.length > 0 && !this.shouldHaveColumn(prop.targetMeta, { ...childProp, name }, [], childFields)) {
1067
+ return [];
1068
+ }
1069
+ return this.mapPropToFieldNames(qb, childProp, tableAlias, childFields);
1070
+ });
1071
+ }
1017
1072
  const aliased = this.platform.quoteIdentifier(`${tableAlias}__${prop.fieldNames[0]}`);
1018
- if (prop.customTypes?.some(type => type?.convertToJSValueSQL)) {
1073
+ if (prop.customTypes?.some(type => !!type?.convertToJSValueSQL)) {
1019
1074
  return prop.fieldNames.map((col, idx) => {
1020
1075
  if (!prop.customTypes[idx]?.convertToJSValueSQL) {
1021
1076
  return col;
@@ -1079,7 +1134,7 @@ export class AbstractSqlDriver extends DatabaseDriver {
1079
1134
  for (const prop of meta.relations) {
1080
1135
  if (collections[prop.name]) {
1081
1136
  const pivotMeta = this.metadata.find(prop.pivotEntity);
1082
- const persister = new PivotCollectionPersister(pivotMeta, this, options?.ctx, options?.schema);
1137
+ const persister = new PivotCollectionPersister(pivotMeta, this, options?.ctx, options?.schema, options?.loggerContext);
1083
1138
  persister.enqueueUpdate(prop, collections[prop.name], clear, pks);
1084
1139
  await this.rethrow(persister.execute());
1085
1140
  }
@@ -1146,7 +1201,7 @@ export class AbstractSqlDriver extends DatabaseDriver {
1146
1201
  let path = parentPath;
1147
1202
  const meta2 = this.metadata.find(prop.type);
1148
1203
  const childOrder = orderHint[prop.name];
1149
- if (![ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind) || !prop.owner || Utils.isPlainObject(childOrder)) {
1204
+ if (prop.kind !== ReferenceKind.SCALAR && (![ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind) || !prop.owner || Utils.isPlainObject(childOrder))) {
1150
1205
  path += `.${propName}`;
1151
1206
  }
1152
1207
  if (prop.kind === ReferenceKind.MANY_TO_MANY && typeof childOrder !== 'object') {
@@ -1154,10 +1209,10 @@ export class AbstractSqlDriver extends DatabaseDriver {
1154
1209
  }
1155
1210
  const join = qb.getJoinForPath(path, { matchPopulateJoins: true });
1156
1211
  const propAlias = qb.getAliasForJoinPath(join ?? path, { matchPopulateJoins: true }) ?? parentAlias;
1157
- if (!join && parentAlias === qb.alias) {
1212
+ if (!join) {
1158
1213
  continue;
1159
1214
  }
1160
- if (![ReferenceKind.SCALAR, ReferenceKind.EMBEDDED].includes(prop.kind) && typeof childOrder === 'object') {
1215
+ if (join && ![ReferenceKind.SCALAR, ReferenceKind.EMBEDDED].includes(prop.kind) && typeof childOrder === 'object') {
1161
1216
  const children = this.buildPopulateOrderBy(qb, meta2, Utils.asArray(childOrder), path, explicit, propAlias);
1162
1217
  orderBy.push(...children);
1163
1218
  continue;
@@ -1235,7 +1290,7 @@ export class AbstractSqlDriver extends DatabaseDriver {
1235
1290
  }
1236
1291
  return ret;
1237
1292
  }
1238
- processField(meta, prop, field, ret, populate, joinedProps, qb) {
1293
+ processField(meta, prop, field, ret) {
1239
1294
  if (!prop || (prop.kind === ReferenceKind.ONE_TO_ONE && !prop.owner)) {
1240
1295
  return;
1241
1296
  }
@@ -1248,7 +1303,7 @@ export class AbstractSqlDriver extends DatabaseDriver {
1248
1303
  const top = parts.shift();
1249
1304
  for (const key of Object.keys(prop.embeddedProps)) {
1250
1305
  if (!top || key === top) {
1251
- this.processField(meta, prop.embeddedProps[key], parts.join('.'), ret, populate, joinedProps, qb);
1306
+ this.processField(meta, prop.embeddedProps[key], parts.join('.'), ret);
1252
1307
  }
1253
1308
  }
1254
1309
  return;
@@ -1258,16 +1313,7 @@ export class AbstractSqlDriver extends DatabaseDriver {
1258
1313
  }
1259
1314
  ret.push(prop.name);
1260
1315
  }
1261
- isPopulated(meta, prop, hint, name) {
1262
- if (hint.field === prop.name || hint.field === name || hint.all) {
1263
- return true;
1264
- }
1265
- if (prop.embedded && hint.children && meta.properties[prop.embedded[0]].name === hint.field) {
1266
- return hint.children.some(c => this.isPopulated(meta, prop, c, prop.embedded[1]));
1267
- }
1268
- return false;
1269
- }
1270
- buildFields(meta, populate, joinedProps, qb, alias, options, count = false) {
1316
+ buildFields(meta, populate, joinedProps, qb, alias, options) {
1271
1317
  const lazyProps = meta.props.filter(prop => prop.lazy && !populate.some(p => this.isPopulated(meta, prop, p)));
1272
1318
  const hasLazyFormulas = meta.props.some(p => p.lazy && p.formula);
1273
1319
  const requiresSQLConversion = meta.props.some(p => p.customType?.convertToJSValueSQL && p.persist !== false);
@@ -1290,7 +1336,7 @@ export class AbstractSqlDriver extends DatabaseDriver {
1290
1336
  where: {},
1291
1337
  aliasMap: qb.getAliasMap(),
1292
1338
  });
1293
- this.processField(meta, prop, parts.join('.'), ret, populate, joinedProps, qb);
1339
+ this.processField(meta, prop, parts.join('.'), ret);
1294
1340
  }
1295
1341
  if (!options.fields.includes('*') && !options.fields.includes(`${qb.alias}.*`)) {
1296
1342
  ret.unshift(...meta.primaryKeys.filter(pk => !options.fields.includes(pk)));
@@ -1300,7 +1346,7 @@ export class AbstractSqlDriver extends DatabaseDriver {
1300
1346
  }
1301
1347
  }
1302
1348
  else if (!Utils.isEmpty(options.exclude) || lazyProps.some(p => !p.formula && (p.kind !== '1:1' || p.owner))) {
1303
- const props = meta.props.filter(prop => this.platform.shouldHaveColumn(prop, populate, options.exclude, false));
1349
+ const props = meta.props.filter(prop => this.platform.shouldHaveColumn(prop, populate, options.exclude, false, false));
1304
1350
  ret.push(...props.filter(p => !lazyProps.includes(p)).map(p => p.name));
1305
1351
  addFormulas = true;
1306
1352
  }
@@ -1312,16 +1358,19 @@ export class AbstractSqlDriver extends DatabaseDriver {
1312
1358
  ret.push('*');
1313
1359
  }
1314
1360
  if (ret.length > 0 && !hasExplicitFields && addFormulas) {
1315
- meta.props
1316
- .filter(prop => prop.formula && !lazyProps.includes(prop))
1317
- .forEach(prop => {
1318
- const a = this.platform.quoteIdentifier(alias);
1319
- const aliased = this.platform.quoteIdentifier(prop.fieldNames[0]);
1320
- ret.push(raw(`${prop.formula(a)} as ${aliased}`));
1321
- });
1322
- meta.props
1323
- .filter(prop => !prop.object && (prop.hasConvertToDatabaseValueSQL || prop.hasConvertToJSValueSQL))
1324
- .forEach(prop => ret.push(prop.name));
1361
+ for (const prop of meta.props) {
1362
+ if (lazyProps.includes(prop)) {
1363
+ continue;
1364
+ }
1365
+ if (prop.formula) {
1366
+ const a = this.platform.quoteIdentifier(alias);
1367
+ const aliased = this.platform.quoteIdentifier(prop.fieldNames[0]);
1368
+ ret.push(raw(`${prop.formula(a)} as ${aliased}`));
1369
+ }
1370
+ if (!prop.object && (prop.hasConvertToDatabaseValueSQL || prop.hasConvertToJSValueSQL)) {
1371
+ ret.push(prop.name);
1372
+ }
1373
+ }
1325
1374
  }
1326
1375
  // add joined relations after the root entity fields
1327
1376
  if (joinedProps.length > 0) {
@@ -1330,7 +1379,6 @@ export class AbstractSqlDriver extends DatabaseDriver {
1330
1379
  exclude: options.exclude,
1331
1380
  populate,
1332
1381
  parentTableAlias: alias,
1333
- count,
1334
1382
  ...options,
1335
1383
  }));
1336
1384
  }