@mikro-orm/sql 7.0.0-dev.97 → 7.0.0-dev.99

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 (91) hide show
  1. package/AbstractSqlConnection.d.ts +57 -0
  2. package/AbstractSqlConnection.js +239 -0
  3. package/AbstractSqlDriver.d.ts +94 -0
  4. package/AbstractSqlDriver.js +1387 -0
  5. package/AbstractSqlPlatform.d.ts +38 -0
  6. package/AbstractSqlPlatform.js +104 -0
  7. package/LICENSE +21 -0
  8. package/PivotCollectionPersister.d.ts +22 -0
  9. package/PivotCollectionPersister.js +159 -0
  10. package/README.md +390 -0
  11. package/SqlEntityManager.d.ts +33 -0
  12. package/SqlEntityManager.js +44 -0
  13. package/SqlEntityRepository.d.ts +19 -0
  14. package/SqlEntityRepository.js +26 -0
  15. package/dialects/index.d.ts +4 -0
  16. package/dialects/index.js +4 -0
  17. package/dialects/mssql/MsSqlNativeQueryBuilder.d.ts +14 -0
  18. package/dialects/mssql/MsSqlNativeQueryBuilder.js +200 -0
  19. package/dialects/mssql/index.d.ts +1 -0
  20. package/dialects/mssql/index.js +1 -0
  21. package/dialects/mysql/MySqlExceptionConverter.d.ts +9 -0
  22. package/dialects/mysql/MySqlExceptionConverter.js +80 -0
  23. package/dialects/mysql/MySqlNativeQueryBuilder.d.ts +7 -0
  24. package/dialects/mysql/MySqlNativeQueryBuilder.js +77 -0
  25. package/dialects/mysql/MySqlPlatform.d.ts +45 -0
  26. package/dialects/mysql/MySqlPlatform.js +116 -0
  27. package/dialects/mysql/MySqlSchemaHelper.d.ts +36 -0
  28. package/dialects/mysql/MySqlSchemaHelper.js +269 -0
  29. package/dialects/mysql/index.d.ts +4 -0
  30. package/dialects/mysql/index.js +4 -0
  31. package/dialects/postgresql/PostgreSqlNativeQueryBuilder.d.ts +5 -0
  32. package/dialects/postgresql/PostgreSqlNativeQueryBuilder.js +8 -0
  33. package/dialects/postgresql/PostgreSqlTableCompiler.d.ts +1 -0
  34. package/dialects/postgresql/PostgreSqlTableCompiler.js +1 -0
  35. package/dialects/postgresql/index.d.ts +1 -0
  36. package/dialects/postgresql/index.js +1 -0
  37. package/dialects/sqlite/BaseSqliteConnection.d.ts +6 -0
  38. package/dialects/sqlite/BaseSqliteConnection.js +8 -0
  39. package/dialects/sqlite/BaseSqlitePlatform.d.ts +70 -0
  40. package/dialects/sqlite/BaseSqlitePlatform.js +104 -0
  41. package/dialects/sqlite/SqliteExceptionConverter.d.ts +9 -0
  42. package/dialects/sqlite/SqliteExceptionConverter.js +54 -0
  43. package/dialects/sqlite/SqliteNativeQueryBuilder.d.ts +6 -0
  44. package/dialects/sqlite/SqliteNativeQueryBuilder.js +11 -0
  45. package/dialects/sqlite/SqliteSchemaHelper.d.ts +38 -0
  46. package/dialects/sqlite/SqliteSchemaHelper.js +379 -0
  47. package/dialects/sqlite/index.d.ts +5 -0
  48. package/dialects/sqlite/index.js +5 -0
  49. package/index.d.ts +19 -0
  50. package/index.js +19 -0
  51. package/package.json +3 -3
  52. package/plugin/index.d.ts +53 -0
  53. package/plugin/index.js +42 -0
  54. package/plugin/transformer.d.ts +115 -0
  55. package/plugin/transformer.js +883 -0
  56. package/query/ArrayCriteriaNode.d.ts +11 -0
  57. package/query/ArrayCriteriaNode.js +24 -0
  58. package/query/CriteriaNode.d.ts +29 -0
  59. package/query/CriteriaNode.js +121 -0
  60. package/query/CriteriaNodeFactory.d.ts +12 -0
  61. package/query/CriteriaNodeFactory.js +90 -0
  62. package/query/NativeQueryBuilder.d.ts +108 -0
  63. package/query/NativeQueryBuilder.js +425 -0
  64. package/query/ObjectCriteriaNode.d.ts +19 -0
  65. package/query/ObjectCriteriaNode.js +249 -0
  66. package/query/QueryBuilder.d.ts +389 -0
  67. package/query/QueryBuilder.js +1558 -0
  68. package/query/QueryBuilderHelper.d.ts +73 -0
  69. package/query/QueryBuilderHelper.js +756 -0
  70. package/query/ScalarCriteriaNode.d.ts +10 -0
  71. package/query/ScalarCriteriaNode.js +49 -0
  72. package/query/enums.d.ts +18 -0
  73. package/query/enums.js +20 -0
  74. package/query/index.d.ts +10 -0
  75. package/query/index.js +10 -0
  76. package/query/raw.d.ts +59 -0
  77. package/query/raw.js +68 -0
  78. package/schema/DatabaseSchema.d.ts +45 -0
  79. package/schema/DatabaseSchema.js +185 -0
  80. package/schema/DatabaseTable.d.ts +68 -0
  81. package/schema/DatabaseTable.js +793 -0
  82. package/schema/SchemaComparator.d.ts +58 -0
  83. package/schema/SchemaComparator.js +577 -0
  84. package/schema/SchemaHelper.d.ts +76 -0
  85. package/schema/SchemaHelper.js +545 -0
  86. package/schema/SqlSchemaGenerator.d.ts +65 -0
  87. package/schema/SqlSchemaGenerator.js +375 -0
  88. package/schema/index.d.ts +5 -0
  89. package/schema/index.js +5 -0
  90. package/typings.d.ts +272 -0
  91. package/typings.js +1 -0
@@ -0,0 +1,1387 @@
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
+ import { QueryBuilder } from './query/QueryBuilder.js';
3
+ import { JoinType, QueryType } from './query/enums.js';
4
+ import { SqlEntityManager } from './SqlEntityManager.js';
5
+ import { PivotCollectionPersister } from './PivotCollectionPersister.js';
6
+ export class AbstractSqlDriver extends DatabaseDriver {
7
+ [EntityManagerType];
8
+ connection;
9
+ replicas = [];
10
+ platform;
11
+ constructor(config, platform, connection, connector) {
12
+ super(config, connector);
13
+ this.connection = new connection(this.config);
14
+ this.replicas = this.createReplicas(conf => new connection(this.config, conf, 'read'));
15
+ this.platform = platform;
16
+ }
17
+ getPlatform() {
18
+ return this.platform;
19
+ }
20
+ createEntityManager(useContext) {
21
+ const EntityManagerClass = this.config.get('entityManager', SqlEntityManager);
22
+ return new EntityManagerClass(this.config, this, this.metadata, useContext);
23
+ }
24
+ async createQueryBuilderFromOptions(meta, where, options = {}) {
25
+ const connectionType = this.resolveConnectionType({ ctx: options.ctx, connectionType: options.connectionType });
26
+ const populate = this.autoJoinOneToOneOwner(meta, options.populate, options.fields);
27
+ const joinedProps = this.joinedProps(meta, populate, options);
28
+ const qb = this.createQueryBuilder(meta.className, options.ctx, connectionType, false, options.logging, undefined, options.em);
29
+ const fields = this.buildFields(meta, populate, joinedProps, qb, qb.alias, options);
30
+ const orderBy = this.buildOrderBy(qb, meta, populate, options);
31
+ const populateWhere = this.buildPopulateWhere(meta, joinedProps, options);
32
+ Utils.asArray(options.flags).forEach(flag => qb.setFlag(flag));
33
+ if (Utils.isPrimaryKey(where, meta.compositePK)) {
34
+ where = { [Utils.getPrimaryKeyHash(meta.primaryKeys)]: where };
35
+ }
36
+ const { first, last, before, after } = options;
37
+ const isCursorPagination = [first, last, before, after].some(v => v != null);
38
+ qb.__populateWhere = options._populateWhere;
39
+ qb.select(fields)
40
+ // only add populateWhere if we are populate-joining, as this will be used to add `on` conditions
41
+ .populate(populate, joinedProps.length > 0 ? populateWhere : undefined, joinedProps.length > 0 ? options.populateFilter : undefined)
42
+ .where(where)
43
+ .groupBy(options.groupBy)
44
+ .having(options.having)
45
+ .indexHint(options.indexHint)
46
+ .comment(options.comments)
47
+ .hintComment(options.hintComments)
48
+ .withSchema(this.getSchemaName(meta, options));
49
+ if (isCursorPagination) {
50
+ const { orderBy: newOrderBy, where } = this.processCursorOptions(meta, options, orderBy);
51
+ qb.andWhere(where).orderBy(newOrderBy);
52
+ }
53
+ else {
54
+ qb.orderBy(orderBy);
55
+ }
56
+ if (options.limit != null || options.offset != null) {
57
+ qb.limit(options.limit, options.offset);
58
+ }
59
+ if (options.lockMode) {
60
+ qb.setLockMode(options.lockMode, options.lockTableAliases);
61
+ }
62
+ if (options.em) {
63
+ await qb.applyJoinedFilters(options.em, options.filters);
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);
74
+ const result = await this.rethrow(qb.execute('all'));
75
+ if (options.last && !options.first) {
76
+ result.reverse();
77
+ }
78
+ return result;
79
+ }
80
+ async findOne(entityName, where, options) {
81
+ const opts = { populate: [], ...options };
82
+ const meta = this.metadata.find(entityName);
83
+ const populate = this.autoJoinOneToOneOwner(meta, opts.populate, opts.fields);
84
+ const joinedProps = this.joinedProps(meta, populate, options);
85
+ const hasToManyJoins = joinedProps.some(hint => this.hasToManyJoins(hint, meta));
86
+ if (joinedProps.length === 0 || !hasToManyJoins) {
87
+ opts.limit = 1;
88
+ }
89
+ if (opts.limit > 0 && !opts.flags?.includes(QueryFlag.DISABLE_PAGINATE)) {
90
+ opts.flags ??= [];
91
+ opts.flags.push(QueryFlag.DISABLE_PAGINATE);
92
+ }
93
+ const res = await this.find(entityName, where, opts);
94
+ return res[0] || null;
95
+ }
96
+ hasToManyJoins(hint, meta) {
97
+ const [propName] = hint.field.split(':', 2);
98
+ const prop = meta.properties[propName];
99
+ if (prop && [ReferenceKind.ONE_TO_MANY, ReferenceKind.MANY_TO_MANY].includes(prop.kind)) {
100
+ return true;
101
+ }
102
+ if (hint.children && prop.targetMeta) {
103
+ return hint.children.some(hint => this.hasToManyJoins(hint, prop.targetMeta));
104
+ }
105
+ return false;
106
+ }
107
+ async findVirtual(entityName, where, options) {
108
+ return this.findFromVirtual(entityName, where, options, QueryType.SELECT);
109
+ }
110
+ async countVirtual(entityName, where, options) {
111
+ return this.findFromVirtual(entityName, where, options, QueryType.COUNT);
112
+ }
113
+ async findFromVirtual(entityName, where, options, type) {
114
+ const meta = this.metadata.get(entityName);
115
+ /* v8 ignore next */
116
+ if (!meta.expression) {
117
+ return type === QueryType.SELECT ? [] : 0;
118
+ }
119
+ if (typeof meta.expression === 'string') {
120
+ return this.wrapVirtualExpressionInSubquery(meta, meta.expression, where, options, type);
121
+ }
122
+ const em = this.createEntityManager();
123
+ em.setTransactionContext(options.ctx);
124
+ const res = meta.expression(em, where, options);
125
+ if (typeof res === 'string') {
126
+ return this.wrapVirtualExpressionInSubquery(meta, res, where, options, type);
127
+ }
128
+ if (res instanceof QueryBuilder) {
129
+ return this.wrapVirtualExpressionInSubquery(meta, res.getFormattedQuery(), where, options, type);
130
+ }
131
+ if (res instanceof RawQueryFragment) {
132
+ const expr = this.platform.formatQuery(res.sql, res.params);
133
+ return this.wrapVirtualExpressionInSubquery(meta, expr, where, options, type);
134
+ }
135
+ /* v8 ignore next */
136
+ return res;
137
+ }
138
+ async *streamFromVirtual(entityName, where, options) {
139
+ const meta = this.metadata.get(entityName);
140
+ /* v8 ignore next */
141
+ if (!meta.expression) {
142
+ return;
143
+ }
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;
163
+ }
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();
178
+ }
179
+ native.from(raw(`(${expression}) as ${this.platform.quoteIdentifier(qb.alias)}`));
180
+ const query = native.compile();
181
+ const res = await this.execute(query.sql, query.params, 'all', options.ctx);
182
+ if (type === QueryType.COUNT) {
183
+ return res[0].count;
184
+ }
185
+ if (isCursorPagination && !options.first && !!options.last) {
186
+ res.reverse();
187
+ }
188
+ return res.map(row => this.mapResult(row, meta));
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
+ }
202
+ mapResult(result, meta, populate = [], qb, map = {}) {
203
+ const ret = super.mapResult(result, meta);
204
+ /* v8 ignore next */
205
+ if (!ret) {
206
+ return null;
207
+ }
208
+ if (qb) {
209
+ // here we map the aliased results (cartesian product) to an object graph
210
+ this.mapJoinedProps(ret, meta, populate, qb, ret, map);
211
+ }
212
+ return ret;
213
+ }
214
+ mapJoinedProps(result, meta, populate, qb, root, map, parentJoinPath) {
215
+ const joinedProps = this.joinedProps(meta, populate);
216
+ joinedProps.forEach(hint => {
217
+ const [propName, ref] = hint.field.split(':', 2);
218
+ const prop = meta.properties[propName];
219
+ /* v8 ignore next */
220
+ if (!prop) {
221
+ return;
222
+ }
223
+ const pivotRefJoin = prop.kind === ReferenceKind.MANY_TO_MANY && ref;
224
+ const meta2 = this.metadata.find(prop.type);
225
+ let path = parentJoinPath ? `${parentJoinPath}.${prop.name}` : `${meta.name}.${prop.name}`;
226
+ if (!parentJoinPath) {
227
+ path = '[populate]' + path;
228
+ }
229
+ if (pivotRefJoin) {
230
+ path += '[pivot]';
231
+ }
232
+ const relationAlias = qb.getAliasForJoinPath(path, { matchPopulateJoins: true });
233
+ /* v8 ignore next */
234
+ if (!relationAlias) {
235
+ return;
236
+ }
237
+ // pivot ref joins via joined strategy need to be handled separately here, as they dont join the target entity
238
+ if (pivotRefJoin) {
239
+ let item;
240
+ if (prop.inverseJoinColumns.length > 1) { // composite keys
241
+ item = prop.inverseJoinColumns.map(name => root[`${relationAlias}__${name}`]);
242
+ }
243
+ else {
244
+ const alias = `${relationAlias}__${prop.inverseJoinColumns[0]}`;
245
+ item = root[alias];
246
+ }
247
+ prop.joinColumns.forEach(name => delete root[`${relationAlias}__${name}`]);
248
+ prop.inverseJoinColumns.forEach(name => delete root[`${relationAlias}__${name}`]);
249
+ result[prop.name] ??= [];
250
+ if (item) {
251
+ result[prop.name].push(item);
252
+ }
253
+ return;
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 || []));
259
+ // If the primary key value for the relation is null, we know we haven't joined to anything
260
+ // and therefore we don't return any record (since all values would be null)
261
+ const hasPK = meta2.getPrimaryProps().every(pk => pk.fieldNames.every(name => {
262
+ return root[`${relationAlias}__${name}`] != null;
263
+ }));
264
+ if (!hasPK) {
265
+ if ([ReferenceKind.MANY_TO_MANY, ReferenceKind.ONE_TO_MANY].includes(prop.kind)) {
266
+ result[prop.name] = [];
267
+ }
268
+ if ([ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind)) {
269
+ result[prop.name] = null;
270
+ }
271
+ for (const prop of targetProps) {
272
+ for (const name of prop.fieldNames) {
273
+ delete root[`${relationAlias}__${name}`];
274
+ }
275
+ }
276
+ return;
277
+ }
278
+ let relationPojo = {};
279
+ meta2.props
280
+ .filter(prop => !ref && prop.persist === false && prop.fieldNames)
281
+ .forEach(prop => {
282
+ /* v8 ignore next */
283
+ if (prop.fieldNames.length > 1) { // composite keys
284
+ relationPojo[prop.name] = prop.fieldNames.map(name => root[`${relationAlias}__${name}`]);
285
+ }
286
+ else {
287
+ const alias = `${relationAlias}__${prop.fieldNames[0]}`;
288
+ relationPojo[prop.name] = root[alias];
289
+ }
290
+ });
291
+ const tz = this.platform.getTimezone();
292
+ for (const prop of targetProps) {
293
+ if (prop.fieldNames.every(name => typeof root[`${relationAlias}__${name}`] === 'undefined')) {
294
+ continue;
295
+ }
296
+ if (prop.fieldNames.length > 1) { // composite keys
297
+ const fk = prop.fieldNames.map(name => root[`${relationAlias}__${name}`]);
298
+ const pk = Utils.mapFlatCompositePrimaryKey(fk, prop);
299
+ relationPojo[prop.name] = pk.every(val => val != null) ? pk : null;
300
+ }
301
+ else if (prop.runtimeType === 'Date') {
302
+ const alias = `${relationAlias}__${prop.fieldNames[0]}`;
303
+ const value = root[alias];
304
+ if (tz && tz !== 'local' && typeof value === 'string' && !value.includes('+') && value.lastIndexOf('-') < 11 && !value.endsWith('Z')) {
305
+ relationPojo[prop.name] = this.platform.parseDate(value + tz);
306
+ }
307
+ else if (['string', 'number'].includes(typeof value)) {
308
+ relationPojo[prop.name] = this.platform.parseDate(value);
309
+ }
310
+ else {
311
+ relationPojo[prop.name] = value;
312
+ }
313
+ }
314
+ else {
315
+ const alias = `${relationAlias}__${prop.fieldNames[0]}`;
316
+ relationPojo[prop.name] = root[alias];
317
+ if (prop.kind === ReferenceKind.EMBEDDED && (prop.object || meta.embeddable)) {
318
+ const item = parseJsonSafe(relationPojo[prop.name]);
319
+ if (Array.isArray(item)) {
320
+ relationPojo[prop.name] = item.map(row => row == null ? row : this.comparator.mapResult(prop.type, row));
321
+ }
322
+ else {
323
+ relationPojo[prop.name] = item == null ? item : this.comparator.mapResult(prop.type, item);
324
+ }
325
+ }
326
+ }
327
+ }
328
+ // properties can be mapped to multiple places, e.g. when sharing a column in multiple FKs,
329
+ // so we need to delete them after everything is mapped from given level
330
+ for (const prop of targetProps) {
331
+ for (const name of prop.fieldNames) {
332
+ delete root[`${relationAlias}__${name}`];
333
+ }
334
+ }
335
+ if (mapToPk) {
336
+ const tmp = Object.values(relationPojo);
337
+ /* v8 ignore next */
338
+ relationPojo = (meta2.compositePK ? tmp : tmp[0]);
339
+ }
340
+ if ([ReferenceKind.MANY_TO_MANY, ReferenceKind.ONE_TO_MANY].includes(prop.kind)) {
341
+ result[prop.name] ??= [];
342
+ result[prop.name].push(relationPojo);
343
+ }
344
+ else {
345
+ result[prop.name] = relationPojo;
346
+ }
347
+ const populateChildren = hint.children || [];
348
+ this.mapJoinedProps(relationPojo, meta2, populateChildren, qb, root, map, path);
349
+ });
350
+ }
351
+ async count(entityName, where, options = {}) {
352
+ const meta = this.metadata.get(entityName);
353
+ if (meta.virtual) {
354
+ return this.countVirtual(entityName, where, options);
355
+ }
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)
366
+ .comment(options.comments)
367
+ .hintComment(options.hintComments)
368
+ .groupBy(options.groupBy)
369
+ .having(options.having)
370
+ .populate(populate, joinedProps.length > 0 ? populateWhere : undefined, joinedProps.length > 0 ? options.populateFilter : undefined)
371
+ .withSchema(this.getSchemaName(meta, options))
372
+ .where(where);
373
+ if (options.em) {
374
+ await qb.applyJoinedFilters(options.em, options.filters);
375
+ }
376
+ return this.rethrow(qb.getCount());
377
+ }
378
+ async nativeInsert(entityName, data, options = {}) {
379
+ options.convertCustomTypes ??= true;
380
+ const meta = this.metadata.find(entityName);
381
+ const collections = this.extractManyToMany(entityName, data);
382
+ const pks = meta?.primaryKeys ?? [this.config.getNamingStrategy().referenceColumnName()];
383
+ const qb = this.createQueryBuilder(entityName, options.ctx, 'write', options.convertCustomTypes, options.loggerContext).withSchema(this.getSchemaName(meta, options));
384
+ const res = await this.rethrow(qb.insert(data).execute('run', false));
385
+ res.row = res.row || {};
386
+ let pk;
387
+ if (pks.length > 1) { // owner has composite pk
388
+ pk = Utils.getPrimaryKeyCond(data, pks);
389
+ }
390
+ else {
391
+ /* v8 ignore next */
392
+ res.insertId = data[pks[0]] ?? res.insertId ?? res.row[pks[0]];
393
+ pk = [res.insertId];
394
+ }
395
+ await this.processManyToMany(meta, pk, collections, false, options);
396
+ return res;
397
+ }
398
+ async nativeInsertMany(entityName, data, options = {}, transform) {
399
+ options.processCollections ??= true;
400
+ options.convertCustomTypes ??= true;
401
+ const meta = this.metadata.find(entityName)?.root;
402
+ const collections = options.processCollections ? data.map(d => this.extractManyToMany(entityName, d)) : [];
403
+ const pks = this.getPrimaryKeyFields(entityName);
404
+ const set = new Set();
405
+ data.forEach(row => Utils.keys(row).forEach(k => set.add(k)));
406
+ const props = [...set].map(name => meta?.properties[name] ?? { name, fieldNames: [name] });
407
+ let fields = Utils.flatten(props.map(prop => prop.fieldNames));
408
+ const duplicates = Utils.findDuplicates(fields);
409
+ const params = [];
410
+ if (duplicates.length) {
411
+ fields = Utils.unique(fields);
412
+ }
413
+ /* v8 ignore next */
414
+ const tableName = meta ? this.getTableName(meta, options) : this.platform.quoteIdentifier(entityName);
415
+ let sql = `insert into ${tableName} `;
416
+ sql += fields.length > 0 ? '(' + fields.map(k => this.platform.quoteIdentifier(k)).join(', ') + ')' : `(${this.platform.quoteIdentifier(pks[0])})`;
417
+ if (meta && this.platform.usesOutputStatement()) {
418
+ const returningProps = meta.props
419
+ .filter(prop => prop.persist !== false && prop.defaultRaw || prop.autoincrement || prop.generated)
420
+ .filter(prop => !(prop.name in data[0]) || isRaw(data[0][prop.name]));
421
+ const returningFields = Utils.flatten(returningProps.map(prop => prop.fieldNames));
422
+ sql += returningFields.length > 0 ? ` output ${returningFields.map(field => 'inserted.' + this.platform.quoteIdentifier(field)).join(', ')}` : '';
423
+ }
424
+ if (fields.length > 0 || this.platform.usesDefaultKeyword()) {
425
+ sql += ' values ';
426
+ }
427
+ else {
428
+ sql += ' ' + data.map(() => `select null as ${this.platform.quoteIdentifier(pks[0])}`).join(' union all ');
429
+ }
430
+ const addParams = (prop, row) => {
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;
437
+ if (prop.kind === ReferenceKind.EMBEDDED && prop.object) {
438
+ if (prop.array && value) {
439
+ value = this.platform.cloneEmbeddable(value);
440
+ for (let i = 0; i < value.length; i++) {
441
+ const item = value[i];
442
+ value[i] = this.mapDataToFieldNames(item, false, prop.embeddedProps, options.convertCustomTypes);
443
+ }
444
+ }
445
+ else {
446
+ value = this.mapDataToFieldNames(value, false, prop.embeddedProps, options.convertCustomTypes);
447
+ }
448
+ }
449
+ if (typeof value === 'undefined' && this.platform.usesDefaultKeyword()) {
450
+ params.push(raw('default'));
451
+ return;
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
+ }
457
+ params.push(value);
458
+ };
459
+ if (fields.length > 0 || this.platform.usesDefaultKeyword()) {
460
+ sql += data.map(row => {
461
+ const keys = [];
462
+ const usedDups = [];
463
+ props.forEach(prop => {
464
+ if (prop.fieldNames.length > 1) {
465
+ const newFields = [];
466
+ const allParam = [...(Utils.asArray(row[prop.name]) ?? prop.fieldNames.map(() => null))];
467
+ // TODO(v7): instead of making this conditional here, the entity snapshot should respect `ownColumns`,
468
+ // but that means changing the compiled PK getters, which might be seen as breaking
469
+ const columns = allParam.length > 1 ? prop.fieldNames : prop.ownColumns;
470
+ const newParam = [];
471
+ columns.forEach((field, idx) => {
472
+ if (usedDups.includes(field)) {
473
+ return;
474
+ }
475
+ newFields.push(field);
476
+ newParam.push(allParam[idx]);
477
+ });
478
+ const param = Utils.flatten(newParam);
479
+ newFields.forEach((field, idx) => {
480
+ if (!duplicates.includes(field) || !usedDups.includes(field)) {
481
+ params.push(param[idx]);
482
+ keys.push('?');
483
+ usedDups.push(field);
484
+ }
485
+ });
486
+ }
487
+ else {
488
+ const field = prop.fieldNames[0];
489
+ if (!duplicates.includes(field) || !usedDups.includes(field)) {
490
+ if (prop.customType && !prop.object && 'convertToDatabaseValueSQL' in prop.customType && row[prop.name] != null && !isRaw(row[prop.name])) {
491
+ keys.push(prop.customType.convertToDatabaseValueSQL('?', this.platform));
492
+ }
493
+ else {
494
+ keys.push('?');
495
+ }
496
+ addParams(prop, row);
497
+ usedDups.push(field);
498
+ }
499
+ }
500
+ });
501
+ return '(' + (keys.join(', ') || 'default') + ')';
502
+ }).join(', ');
503
+ }
504
+ if (meta && this.platform.usesReturningStatement()) {
505
+ const returningProps = meta.props
506
+ .filter(prop => prop.persist !== false && prop.defaultRaw || prop.autoincrement || prop.generated)
507
+ .filter(prop => !(prop.name in data[0]) || isRaw(data[0][prop.name]));
508
+ const returningFields = Utils.flatten(returningProps.map(prop => prop.fieldNames));
509
+ /* v8 ignore next */
510
+ sql += returningFields.length > 0 ? ` returning ${returningFields.map(field => this.platform.quoteIdentifier(field)).join(', ')}` : '';
511
+ }
512
+ if (transform) {
513
+ sql = transform(sql);
514
+ }
515
+ const res = await this.execute(sql, params, 'run', options.ctx, options.loggerContext);
516
+ let pk;
517
+ /* v8 ignore next */
518
+ if (pks.length > 1) { // owner has composite pk
519
+ pk = data.map(d => Utils.getPrimaryKeyCond(d, pks));
520
+ }
521
+ else {
522
+ res.row ??= {};
523
+ res.rows ??= [];
524
+ pk = data.map((d, i) => d[pks[0]] ?? res.rows[i]?.[pks[0]]).map(d => [d]);
525
+ res.insertId = res.insertId || res.row[pks[0]];
526
+ }
527
+ for (let i = 0; i < collections.length; i++) {
528
+ await this.processManyToMany(meta, pk[i], collections[i], false, options);
529
+ }
530
+ return res;
531
+ }
532
+ async nativeUpdate(entityName, where, data, options = {}) {
533
+ options.convertCustomTypes ??= true;
534
+ const meta = this.metadata.find(entityName);
535
+ const pks = this.getPrimaryKeyFields(entityName);
536
+ const collections = this.extractManyToMany(entityName, data);
537
+ let res = { affectedRows: 0, insertId: 0, row: {} };
538
+ if (Utils.isPrimaryKey(where) && pks.length === 1) {
539
+ /* v8 ignore next */
540
+ where = { [meta?.primaryKeys[0] ?? pks[0]]: where };
541
+ }
542
+ if (Utils.hasObjectKeys(data)) {
543
+ const qb = this.createQueryBuilder(entityName, options.ctx, 'write', options.convertCustomTypes, options.loggerContext)
544
+ .withSchema(this.getSchemaName(meta, options));
545
+ if (options.upsert) {
546
+ /* v8 ignore next */
547
+ const uniqueFields = options.onConflictFields ?? (Utils.isPlainObject(where) ? Utils.keys(where) : meta.primaryKeys);
548
+ const returning = getOnConflictReturningFields(meta, data, uniqueFields, options);
549
+ qb.insert(data)
550
+ .onConflict(uniqueFields)
551
+ .returning(returning);
552
+ if (!options.onConflictAction || options.onConflictAction === 'merge') {
553
+ const fields = getOnConflictFields(meta, data, uniqueFields, options);
554
+ qb.merge(fields);
555
+ }
556
+ if (options.onConflictAction === 'ignore') {
557
+ qb.ignore();
558
+ }
559
+ }
560
+ else {
561
+ qb.update(data).where(where);
562
+ // reload generated columns and version fields
563
+ const returning = [];
564
+ meta?.props
565
+ .filter(prop => (prop.generated && !prop.primary) || prop.version)
566
+ .forEach(prop => returning.push(prop.name));
567
+ qb.returning(returning);
568
+ }
569
+ res = await this.rethrow(qb.execute('run', false));
570
+ }
571
+ /* v8 ignore next */
572
+ const pk = pks.map(pk => Utils.extractPK(data[pk] || where, meta));
573
+ await this.processManyToMany(meta, pk, collections, true, options);
574
+ return res;
575
+ }
576
+ async nativeUpdateMany(entityName, where, data, options = {}) {
577
+ options.processCollections ??= true;
578
+ options.convertCustomTypes ??= true;
579
+ const meta = this.metadata.get(entityName);
580
+ if (options.upsert) {
581
+ const uniqueFields = options.onConflictFields ?? (Utils.isPlainObject(where[0]) ? Object.keys(where[0]).flatMap(key => Utils.splitPrimaryKeys(key)) : meta.primaryKeys);
582
+ const qb = this.createQueryBuilder(entityName, options.ctx, 'write', options.convertCustomTypes, options.loggerContext).withSchema(this.getSchemaName(meta, options));
583
+ const returning = getOnConflictReturningFields(meta, data[0], uniqueFields, options);
584
+ qb.insert(data)
585
+ .onConflict(uniqueFields)
586
+ .returning(returning);
587
+ if (!options.onConflictAction || options.onConflictAction === 'merge') {
588
+ const fields = getOnConflictFields(meta, data[0], uniqueFields, options);
589
+ qb.merge(fields);
590
+ }
591
+ if (options.onConflictAction === 'ignore') {
592
+ qb.ignore();
593
+ }
594
+ return this.rethrow(qb.execute('run', false));
595
+ }
596
+ const collections = options.processCollections ? data.map(d => this.extractManyToMany(entityName, d)) : [];
597
+ const keys = new Set();
598
+ const fields = new Set();
599
+ const returning = new Set();
600
+ for (const row of data) {
601
+ for (const k of Utils.keys(row)) {
602
+ keys.add(k);
603
+ if (isRaw(row[k])) {
604
+ returning.add(k);
605
+ }
606
+ }
607
+ }
608
+ // reload generated columns and version fields
609
+ meta?.props
610
+ .filter(prop => prop.generated || prop.version || prop.primary)
611
+ .forEach(prop => returning.add(prop.name));
612
+ const pkCond = Utils.flatten(meta.primaryKeys.map(pk => meta.properties[pk].fieldNames)).map(pk => `${this.platform.quoteIdentifier(pk)} = ?`).join(' and ');
613
+ const params = [];
614
+ let sql = `update ${this.getTableName(meta, options)} set `;
615
+ const addParams = (prop, value) => {
616
+ if (prop.kind === ReferenceKind.EMBEDDED && prop.object) {
617
+ if (prop.array && value) {
618
+ for (let i = 0; i < value.length; i++) {
619
+ const item = value[i];
620
+ value[i] = this.mapDataToFieldNames(item, false, prop.embeddedProps, options.convertCustomTypes);
621
+ }
622
+ }
623
+ else {
624
+ value = this.mapDataToFieldNames(value, false, prop.embeddedProps, options.convertCustomTypes);
625
+ }
626
+ }
627
+ params.push(value);
628
+ };
629
+ for (const key of keys) {
630
+ const prop = meta.properties[key] ?? meta.root.properties[key];
631
+ prop.fieldNames.forEach((fieldName, fieldNameIdx) => {
632
+ if (fields.has(fieldName) || (prop.ownColumns && !prop.ownColumns.includes(fieldName))) {
633
+ return;
634
+ }
635
+ fields.add(fieldName);
636
+ sql += `${this.platform.quoteIdentifier(fieldName)} = case`;
637
+ where.forEach((cond, idx) => {
638
+ if (key in data[idx]) {
639
+ const pks = Utils.getOrderedPrimaryKeys(cond, meta);
640
+ sql += ` when (${pkCond}) then `;
641
+ if (prop.customType && !prop.object && 'convertToDatabaseValueSQL' in prop.customType && data[idx][prop.name] != null && !isRaw(data[idx][key])) {
642
+ sql += prop.customType.convertToDatabaseValueSQL('?', this.platform);
643
+ }
644
+ else {
645
+ sql += '?';
646
+ }
647
+ params.push(...pks);
648
+ addParams(prop, prop.fieldNames.length > 1 ? data[idx][key]?.[fieldNameIdx] : data[idx][key]);
649
+ }
650
+ });
651
+ sql += ` else ${this.platform.quoteIdentifier(fieldName)} end, `;
652
+ return sql;
653
+ });
654
+ }
655
+ if (meta.versionProperty) {
656
+ const versionProperty = meta.properties[meta.versionProperty];
657
+ const quotedFieldName = this.platform.quoteIdentifier(versionProperty.fieldNames[0]);
658
+ sql += `${quotedFieldName} = `;
659
+ if (versionProperty.runtimeType === 'Date') {
660
+ sql += this.platform.getCurrentTimestampSQL(versionProperty.length);
661
+ }
662
+ else {
663
+ sql += `${quotedFieldName} + 1`;
664
+ }
665
+ sql += `, `;
666
+ }
667
+ sql = sql.substring(0, sql.length - 2) + ' where ';
668
+ const pkProps = meta.primaryKeys.concat(...meta.concurrencyCheckKeys);
669
+ const pks = Utils.flatten(pkProps.map(pk => meta.properties[pk].fieldNames));
670
+ sql += pks.length > 1 ? `(${pks.map(pk => `${this.platform.quoteIdentifier(pk)}`).join(', ')})` : this.platform.quoteIdentifier(pks[0]);
671
+ const conds = where.map(cond => {
672
+ if (Utils.isPlainObject(cond) && Utils.getObjectKeysSize(cond) === 1) {
673
+ cond = Object.values(cond)[0];
674
+ }
675
+ if (pks.length > 1) {
676
+ pkProps.forEach(pk => {
677
+ if (Array.isArray(cond[pk])) {
678
+ params.push(...Utils.flatten(cond[pk]));
679
+ }
680
+ else {
681
+ params.push(cond[pk]);
682
+ }
683
+ });
684
+ return `(${Array.from({ length: pks.length }).fill('?').join(', ')})`;
685
+ }
686
+ params.push(cond);
687
+ return '?';
688
+ });
689
+ sql += ` in (${conds.join(', ')})`;
690
+ if (this.platform.usesReturningStatement() && returning.size > 0) {
691
+ const returningFields = Utils.flatten([...returning].map(prop => meta.properties[prop].fieldNames));
692
+ /* v8 ignore next */
693
+ sql += returningFields.length > 0 ? ` returning ${returningFields.map(field => this.platform.quoteIdentifier(field)).join(', ')}` : '';
694
+ }
695
+ const res = await this.rethrow(this.execute(sql, params, 'run', options.ctx, options.loggerContext));
696
+ for (let i = 0; i < collections.length; i++) {
697
+ await this.processManyToMany(meta, where[i], collections[i], false, options);
698
+ }
699
+ return res;
700
+ }
701
+ async nativeDelete(entityName, where, options = {}) {
702
+ const meta = this.metadata.find(entityName);
703
+ const pks = this.getPrimaryKeyFields(entityName);
704
+ if (Utils.isPrimaryKey(where) && pks.length === 1) {
705
+ where = { [pks[0]]: where };
706
+ }
707
+ const qb = this.createQueryBuilder(entityName, options.ctx, 'write', false, options.loggerContext).delete(where).withSchema(this.getSchemaName(meta, options));
708
+ return this.rethrow(qb.execute('run', false));
709
+ }
710
+ /**
711
+ * Fast comparison for collection snapshots that are represented by PK arrays.
712
+ * Compares scalars via `===` and fallbacks to Utils.equals()` for more complex types like Buffer.
713
+ * Always expects the same length of the arrays, since we only compare PKs of the same entity type.
714
+ */
715
+ comparePrimaryKeyArrays(a, b) {
716
+ for (let i = a.length; i-- !== 0;) {
717
+ if (['number', 'string', 'bigint', 'boolean'].includes(typeof a[i])) {
718
+ if (a[i] !== b[i]) {
719
+ return false;
720
+ }
721
+ }
722
+ else {
723
+ if (!Utils.equals(a[i], b[i])) {
724
+ return false;
725
+ }
726
+ }
727
+ }
728
+ return true;
729
+ }
730
+ async syncCollections(collections, options) {
731
+ const groups = {};
732
+ for (const coll of collections) {
733
+ const wrapped = helper(coll.owner);
734
+ const meta = wrapped.__meta;
735
+ const pks = wrapped.getPrimaryKeys(true);
736
+ const snap = coll.getSnapshot();
737
+ const includes = (arr, item) => !!arr.find(i => this.comparePrimaryKeyArrays(i, item));
738
+ const snapshot = snap ? snap.map(item => helper(item).getPrimaryKeys(true)) : [];
739
+ const current = coll.getItems(false).map(item => helper(item).getPrimaryKeys(true));
740
+ const deleteDiff = snap ? snapshot.filter(item => !includes(current, item)) : true;
741
+ const insertDiff = current.filter(item => !includes(snapshot, item));
742
+ const target = snapshot.filter(item => includes(current, item)).concat(...insertDiff);
743
+ const equals = Utils.equals(current, target);
744
+ // wrong order if we just delete and insert to the end (only owning sides can have fixed order)
745
+ if (coll.property.owner && coll.property.fixedOrder && !equals && Array.isArray(deleteDiff)) {
746
+ deleteDiff.length = insertDiff.length = 0;
747
+ for (const item of snapshot) {
748
+ deleteDiff.push(item);
749
+ }
750
+ for (const item of current) {
751
+ insertDiff.push(item);
752
+ }
753
+ }
754
+ if (coll.property.kind === ReferenceKind.ONE_TO_MANY) {
755
+ const cols = coll.property.referencedColumnNames;
756
+ const qb = this.createQueryBuilder(coll.property.type, options?.ctx, 'write')
757
+ .withSchema(this.getSchemaName(meta, options));
758
+ if (coll.getSnapshot() === undefined) {
759
+ if (coll.property.orphanRemoval) {
760
+ const query = qb.delete({ [coll.property.mappedBy]: pks })
761
+ .andWhere({ [cols.join(Utils.PK_SEPARATOR)]: { $nin: insertDiff } });
762
+ await this.rethrow(query.execute());
763
+ continue;
764
+ }
765
+ const query = qb.update({ [coll.property.mappedBy]: null })
766
+ .where({ [coll.property.mappedBy]: pks })
767
+ .andWhere({ [cols.join(Utils.PK_SEPARATOR)]: { $nin: insertDiff } });
768
+ await this.rethrow(query.execute());
769
+ continue;
770
+ }
771
+ /* v8 ignore next */
772
+ const query = qb.update({ [coll.property.mappedBy]: pks })
773
+ .where({ [cols.join(Utils.PK_SEPARATOR)]: { $in: insertDiff } });
774
+ await this.rethrow(query.execute());
775
+ continue;
776
+ }
777
+ /* v8 ignore next */
778
+ const pivotMeta = this.metadata.find(coll.property.pivotEntity);
779
+ let schema = pivotMeta.schema;
780
+ if (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
+ }
789
+ }
790
+ else if (schema == null) {
791
+ schema = this.config.get('schema');
792
+ }
793
+ const tableName = `${schema ?? '_'}.${pivotMeta.tableName}`;
794
+ const persister = groups[tableName] ??= new PivotCollectionPersister(pivotMeta, this, options?.ctx, schema, options?.loggerContext);
795
+ persister.enqueueUpdate(coll.property, insertDiff, deleteDiff, pks, coll.isInitialized());
796
+ }
797
+ for (const persister of Utils.values(groups)) {
798
+ await this.rethrow(persister.execute());
799
+ }
800
+ }
801
+ async loadFromPivotTable(prop, owners, where = {}, orderBy, ctx, options, pivotJoin) {
802
+ if (owners.length === 0) {
803
+ return {};
804
+ }
805
+ const pivotMeta = this.metadata.find(prop.pivotEntity);
806
+ const pivotProp1 = pivotMeta.relations[prop.owner ? 1 : 0];
807
+ const pivotProp2 = pivotMeta.relations[prop.owner ? 0 : 1];
808
+ const ownerMeta = this.metadata.find(pivotProp2.type);
809
+ const cond = {
810
+ [pivotProp2.name]: { $in: ownerMeta.compositePK ? owners : owners.map(o => o[0]) },
811
+ };
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,
834
+ });
835
+ const map = {};
836
+ for (const owner of owners) {
837
+ const key = Utils.getPrimaryKeyHash(owner);
838
+ map[key] = [];
839
+ }
840
+ for (const item of res) {
841
+ const key = Utils.getPrimaryKeyHash(Utils.asArray(item[pivotProp2.name]));
842
+ map[key].push(item[pivotProp1.name]);
843
+ }
844
+ return map;
845
+ }
846
+ getPivotOrderBy(prop, pivotProp, orderBy, parentOrderBy) {
847
+ if (!Utils.isEmpty(orderBy)) {
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] }));
854
+ }
855
+ if (!Utils.isEmpty(prop.orderBy)) {
856
+ return Utils.asArray(prop.orderBy).map(o => ({ [pivotProp.name]: o }));
857
+ }
858
+ if (prop.fixedOrder) {
859
+ return [{ [prop.fixedOrderColumn]: QueryOrder.ASC }];
860
+ }
861
+ return [];
862
+ }
863
+ async execute(query, params = [], method = 'all', ctx, loggerContext) {
864
+ return this.rethrow(this.connection.execute(query, params, method, ctx, loggerContext));
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
+ }
884
+ /**
885
+ * 1:1 owner side needs to be marked for population so QB auto-joins the owner id
886
+ */
887
+ autoJoinOneToOneOwner(meta, populate, fields = []) {
888
+ if (!this.config.get('autoJoinOneToOneOwner')) {
889
+ return populate;
890
+ }
891
+ const relationsToPopulate = populate.map(({ field }) => field.split(':')[0]);
892
+ const toPopulate = meta.relations
893
+ .filter(prop => prop.kind === ReferenceKind.ONE_TO_ONE && !prop.owner && !prop.lazy && !relationsToPopulate.includes(prop.name))
894
+ .filter(prop => fields.length === 0 || fields.some(f => prop.name === f || prop.name.startsWith(`${String(f)}.`)))
895
+ .map(prop => ({ field: `${prop.name}:ref`, strategy: LoadStrategy.JOINED }));
896
+ return [...populate, ...toPopulate];
897
+ }
898
+ /**
899
+ * @internal
900
+ */
901
+ joinedProps(meta, populate, options) {
902
+ return populate.filter(hint => {
903
+ const [propName, ref] = hint.field.split(':', 2);
904
+ const prop = meta.properties[propName] || {};
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)) {
907
+ return true;
908
+ }
909
+ // skip redundant joins for 1:1 owner population hints when using `mapToPk`
910
+ if (prop.kind === ReferenceKind.ONE_TO_ONE && prop.mapToPk && prop.owner) {
911
+ return false;
912
+ }
913
+ if (strategy !== LoadStrategy.JOINED) {
914
+ // force joined strategy for explicit 1:1 owner populate hint as it would require a join anyway
915
+ return prop.kind === ReferenceKind.ONE_TO_ONE && !prop.owner;
916
+ }
917
+ return ![ReferenceKind.SCALAR, ReferenceKind.EMBEDDED].includes(prop.kind);
918
+ });
919
+ }
920
+ /**
921
+ * @internal
922
+ */
923
+ mergeJoinedResult(rawResults, meta, joinedProps) {
924
+ if (rawResults.length <= 1) {
925
+ return rawResults;
926
+ }
927
+ const res = [];
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
+ });
934
+ for (const item of rawResults) {
935
+ const pk = Utils.getCompositeKeyHash(item, meta);
936
+ if (map[pk]) {
937
+ for (const { propName } of hints) {
938
+ if (!item[propName]) {
939
+ continue;
940
+ }
941
+ collectionsToMerge[pk] ??= {};
942
+ collectionsToMerge[pk][propName] ??= [map[pk][propName]];
943
+ collectionsToMerge[pk][propName].push(item[propName]);
944
+ }
945
+ }
946
+ else {
947
+ map[pk] = item;
948
+ res.push(item);
949
+ }
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
+ }
976
+ return res;
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
+ }
987
+ getFieldsForJoinedLoad(qb, meta, options) {
988
+ const fields = [];
989
+ const populate = options.populate ?? [];
990
+ const joinedProps = this.joinedProps(meta, populate, options);
991
+ const populateWhereAll = options?._populateWhere === 'all' || Utils.isEmpty(options?._populateWhere);
992
+ // root entity is already handled, skip that
993
+ if (options.parentJoinPath) {
994
+ // alias all fields in the primary table
995
+ meta.props
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)));
998
+ }
999
+ for (const hint of joinedProps) {
1000
+ const [propName, ref] = hint.field.split(':', 2);
1001
+ const prop = meta.properties[propName];
1002
+ // ignore ref joins of known FKs unless it's a filter hint
1003
+ if (ref && !hint.filter && (prop.kind === ReferenceKind.MANY_TO_ONE || (prop.kind === ReferenceKind.ONE_TO_ONE && prop.owner))) {
1004
+ continue;
1005
+ }
1006
+ const meta2 = this.metadata.find(prop.type);
1007
+ const pivotRefJoin = prop.kind === ReferenceKind.MANY_TO_MANY && ref;
1008
+ const tableAlias = qb.getNextAlias(prop.name);
1009
+ const field = `${options.parentTableAlias}.${prop.name}`;
1010
+ let path = options.parentJoinPath ? `${options.parentJoinPath}.${prop.name}` : `${meta.name}.${prop.name}`;
1011
+ if (!options.parentJoinPath && populateWhereAll && !hint.filter && !path.startsWith('[populate]')) {
1012
+ path = '[populate]' + path;
1013
+ }
1014
+ const mandatoryToOneProperty = [ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind) && !prop.nullable;
1015
+ const joinType = pivotRefJoin
1016
+ ? JoinType.pivotJoin
1017
+ : hint.joinType
1018
+ ? hint.joinType
1019
+ : (hint.filter && !prop.nullable) || mandatoryToOneProperty
1020
+ ? JoinType.innerJoin
1021
+ : JoinType.leftJoin;
1022
+ qb.join(field, tableAlias, {}, joinType, path);
1023
+ if (pivotRefJoin) {
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}`)));
1025
+ }
1026
+ if (prop.kind === ReferenceKind.ONE_TO_MANY && ref) {
1027
+ fields.push(...this.getFieldsForJoinedLoad(qb, meta2, {
1028
+ ...options,
1029
+ explicitFields: prop.referencedColumnNames,
1030
+ exclude: undefined,
1031
+ populate: hint.children,
1032
+ parentTableAlias: tableAlias,
1033
+ parentJoinPath: path,
1034
+ }));
1035
+ }
1036
+ const childExplicitFields = options.explicitFields?.filter(f => Utils.isPlainObject(f)).map(o => o[prop.name])[0] || [];
1037
+ options.explicitFields?.forEach(f => {
1038
+ if (typeof f === 'string' && f.startsWith(`${prop.name}.`)) {
1039
+ childExplicitFields.push(f.substring(prop.name.length + 1));
1040
+ }
1041
+ });
1042
+ const childExclude = options.exclude ? Utils.extractChildElements(options.exclude, prop.name) : options.exclude;
1043
+ if (!ref && !prop.mapToPk) {
1044
+ fields.push(...this.getFieldsForJoinedLoad(qb, meta2, {
1045
+ ...options,
1046
+ explicitFields: childExplicitFields.length === 0 ? undefined : childExplicitFields,
1047
+ exclude: childExclude,
1048
+ populate: hint.children,
1049
+ parentTableAlias: tableAlias,
1050
+ parentJoinPath: path,
1051
+ }));
1052
+ }
1053
+ else if (hint.filter || prop.mapToPk || (ref && [ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind))) {
1054
+ fields.push(...prop.referencedColumnNames.map(col => qb.helper.mapper(`${tableAlias}.${col}`, qb.type, undefined, `${tableAlias}__${col}`)));
1055
+ }
1056
+ }
1057
+ return fields;
1058
+ }
1059
+ /**
1060
+ * @internal
1061
+ */
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 (!this.shouldHaveColumn(prop.targetMeta, { ...childProp, name }, [], childFields.length > 0 ? childFields : undefined)) {
1067
+ return [];
1068
+ }
1069
+ return this.mapPropToFieldNames(qb, childProp, tableAlias, childFields);
1070
+ });
1071
+ }
1072
+ const aliased = this.platform.quoteIdentifier(`${tableAlias}__${prop.fieldNames[0]}`);
1073
+ if (prop.customTypes?.some(type => !!type?.convertToJSValueSQL)) {
1074
+ return prop.fieldNames.map((col, idx) => {
1075
+ if (!prop.customTypes[idx]?.convertToJSValueSQL) {
1076
+ return col;
1077
+ }
1078
+ const prefixed = this.platform.quoteIdentifier(`${tableAlias}.${col}`);
1079
+ const aliased = this.platform.quoteIdentifier(`${tableAlias}__${col}`);
1080
+ return raw(`${prop.customTypes[idx].convertToJSValueSQL(prefixed, this.platform)} as ${aliased}`);
1081
+ });
1082
+ }
1083
+ if (prop.customType?.convertToJSValueSQL) {
1084
+ const prefixed = this.platform.quoteIdentifier(`${tableAlias}.${prop.fieldNames[0]}`);
1085
+ return [raw(`${prop.customType.convertToJSValueSQL(prefixed, this.platform)} as ${aliased}`)];
1086
+ }
1087
+ if (prop.formula) {
1088
+ const alias = this.platform.quoteIdentifier(tableAlias);
1089
+ return [raw(`${prop.formula(alias)} as ${aliased}`)];
1090
+ }
1091
+ return prop.fieldNames.map(fieldName => {
1092
+ return `${tableAlias}.${fieldName} as ${tableAlias}__${fieldName}`;
1093
+ });
1094
+ }
1095
+ /** @internal */
1096
+ createQueryBuilder(entityName, ctx, preferredConnectionType, convertCustomTypes, loggerContext, alias, em) {
1097
+ // do not compute the connectionType if EM is provided as it will be computed from it in the QB later on
1098
+ const connectionType = em ? preferredConnectionType : this.resolveConnectionType({ ctx, connectionType: preferredConnectionType });
1099
+ const qb = new QueryBuilder(entityName, this.metadata, this, ctx, alias, connectionType, em, loggerContext);
1100
+ if (!convertCustomTypes) {
1101
+ qb.unsetFlag(QueryFlag.CONVERT_CUSTOM_TYPES);
1102
+ }
1103
+ return qb;
1104
+ }
1105
+ resolveConnectionType(args) {
1106
+ if (args.ctx) {
1107
+ return 'write';
1108
+ }
1109
+ if (args.connectionType) {
1110
+ return args.connectionType;
1111
+ }
1112
+ if (this.config.get('preferReadReplicas')) {
1113
+ return 'read';
1114
+ }
1115
+ return 'write';
1116
+ }
1117
+ extractManyToMany(entityName, data) {
1118
+ if (!this.metadata.has(entityName)) {
1119
+ return {};
1120
+ }
1121
+ const ret = {};
1122
+ this.metadata.find(entityName).relations.forEach(prop => {
1123
+ if (prop.kind === ReferenceKind.MANY_TO_MANY && data[prop.name]) {
1124
+ ret[prop.name] = data[prop.name].map((item) => Utils.asArray(item));
1125
+ delete data[prop.name];
1126
+ }
1127
+ });
1128
+ return ret;
1129
+ }
1130
+ async processManyToMany(meta, pks, collections, clear, options) {
1131
+ if (!meta) {
1132
+ return;
1133
+ }
1134
+ for (const prop of meta.relations) {
1135
+ if (collections[prop.name]) {
1136
+ const pivotMeta = this.metadata.find(prop.pivotEntity);
1137
+ const persister = new PivotCollectionPersister(pivotMeta, this, options?.ctx, options?.schema, options?.loggerContext);
1138
+ persister.enqueueUpdate(prop, collections[prop.name], clear, pks);
1139
+ await this.rethrow(persister.execute());
1140
+ }
1141
+ }
1142
+ }
1143
+ async lockPessimistic(entity, options) {
1144
+ const meta = helper(entity).__meta;
1145
+ const qb = this.createQueryBuilder(entity.constructor.name, options.ctx, undefined, undefined, options.logging).withSchema(options.schema ?? meta.schema);
1146
+ const cond = Utils.getPrimaryKeyCond(entity, meta.primaryKeys);
1147
+ qb.select(raw('1')).where(cond).setLockMode(options.lockMode, options.lockTableAliases);
1148
+ await this.rethrow(qb.execute());
1149
+ }
1150
+ buildPopulateWhere(meta, joinedProps, options) {
1151
+ const where = {};
1152
+ for (const hint of joinedProps) {
1153
+ const [propName] = hint.field.split(':', 2);
1154
+ const prop = meta.properties[propName];
1155
+ if (!Utils.isEmpty(prop.where)) {
1156
+ where[prop.name] = Utils.copy(prop.where);
1157
+ }
1158
+ if (hint.children) {
1159
+ const inner = this.buildPopulateWhere(prop.targetMeta, hint.children, {});
1160
+ if (!Utils.isEmpty(inner)) {
1161
+ where[prop.name] ??= {};
1162
+ Object.assign(where[prop.name], inner);
1163
+ }
1164
+ }
1165
+ }
1166
+ if (Utils.isEmpty(options.populateWhere)) {
1167
+ return where;
1168
+ }
1169
+ if (Utils.isEmpty(where)) {
1170
+ return options.populateWhere;
1171
+ }
1172
+ /* v8 ignore next */
1173
+ return { $and: [options.populateWhere, where] };
1174
+ }
1175
+ buildOrderBy(qb, meta, populate, options) {
1176
+ const joinedProps = this.joinedProps(meta, populate, options);
1177
+ // `options._populateWhere` is a copy of the value provided by user with a fallback to the global config option
1178
+ // as `options.populateWhere` will be always recomputed to respect filters
1179
+ const populateWhereAll = options._populateWhere !== 'infer' && !Utils.isEmpty(options._populateWhere);
1180
+ const path = (populateWhereAll ? '[populate]' : '') + meta.className;
1181
+ const populateOrderBy = this.buildPopulateOrderBy(qb, meta, Utils.asArray(options.populateOrderBy ?? options.orderBy), path, !!options.populateOrderBy);
1182
+ const joinedPropsOrderBy = this.buildJoinedPropsOrderBy(qb, meta, joinedProps, options, path);
1183
+ return [...Utils.asArray(options.orderBy), ...populateOrderBy, ...joinedPropsOrderBy];
1184
+ }
1185
+ buildPopulateOrderBy(qb, meta, populateOrderBy, parentPath, explicit, parentAlias = qb.alias) {
1186
+ const orderBy = [];
1187
+ for (let i = 0; i < populateOrderBy.length; i++) {
1188
+ const orderHint = populateOrderBy[i];
1189
+ for (const propName of Utils.keys(orderHint)) {
1190
+ const raw = RawQueryFragment.getKnownFragment(propName, explicit);
1191
+ if (raw) {
1192
+ const sql = raw.sql.replace(new RegExp(ALIAS_REPLACEMENT_RE, 'g'), parentAlias);
1193
+ const raw2 = new RawQueryFragment(sql, raw.params);
1194
+ orderBy.push({ [raw2]: orderHint[propName] });
1195
+ continue;
1196
+ }
1197
+ const prop = meta.properties[propName];
1198
+ if (!prop) {
1199
+ throw new Error(`Trying to order by not existing property ${meta.className}.${propName}`);
1200
+ }
1201
+ let path = parentPath;
1202
+ const meta2 = this.metadata.find(prop.type);
1203
+ const childOrder = orderHint[prop.name];
1204
+ if (prop.kind !== ReferenceKind.SCALAR && (![ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind) || !prop.owner || Utils.isPlainObject(childOrder))) {
1205
+ path += `.${propName}`;
1206
+ }
1207
+ if (prop.kind === ReferenceKind.MANY_TO_MANY && typeof childOrder !== 'object') {
1208
+ path += '[pivot]';
1209
+ }
1210
+ const join = qb.getJoinForPath(path, { matchPopulateJoins: true });
1211
+ const propAlias = qb.getAliasForJoinPath(join ?? path, { matchPopulateJoins: true }) ?? parentAlias;
1212
+ if (!join) {
1213
+ continue;
1214
+ }
1215
+ if (join && ![ReferenceKind.SCALAR, ReferenceKind.EMBEDDED].includes(prop.kind) && typeof childOrder === 'object') {
1216
+ const children = this.buildPopulateOrderBy(qb, meta2, Utils.asArray(childOrder), path, explicit, propAlias);
1217
+ orderBy.push(...children);
1218
+ continue;
1219
+ }
1220
+ if (prop.kind === ReferenceKind.MANY_TO_MANY && join) {
1221
+ if (prop.fixedOrderColumn) {
1222
+ orderBy.push({ [`${join.alias}.${prop.fixedOrderColumn}`]: childOrder });
1223
+ }
1224
+ else {
1225
+ for (const col of prop.inverseJoinColumns) {
1226
+ orderBy.push({ [`${join.ownerAlias}.${col}`]: childOrder });
1227
+ }
1228
+ }
1229
+ continue;
1230
+ }
1231
+ const order = typeof childOrder === 'object' ? childOrder[propName] : childOrder;
1232
+ if (order) {
1233
+ orderBy.push({ [`${propAlias}.${propName}`]: order });
1234
+ }
1235
+ }
1236
+ }
1237
+ return orderBy;
1238
+ }
1239
+ buildJoinedPropsOrderBy(qb, meta, populate, options, parentPath) {
1240
+ const orderBy = [];
1241
+ const joinedProps = this.joinedProps(meta, populate, options);
1242
+ for (const hint of joinedProps) {
1243
+ const [propName, ref] = hint.field.split(':', 2);
1244
+ const prop = meta.properties[propName];
1245
+ const propOrderBy = prop.orderBy;
1246
+ let path = `${parentPath}.${propName}`;
1247
+ if (prop.kind === ReferenceKind.MANY_TO_MANY && ref) {
1248
+ path += '[pivot]';
1249
+ }
1250
+ const join = qb.getJoinForPath(path, { matchPopulateJoins: true });
1251
+ const propAlias = qb.getAliasForJoinPath(join ?? path, { matchPopulateJoins: true });
1252
+ const meta2 = this.metadata.find(prop.type);
1253
+ if (prop.kind === ReferenceKind.MANY_TO_MANY && prop.fixedOrder && join) {
1254
+ const alias = ref ? propAlias : join.ownerAlias;
1255
+ orderBy.push({ [`${alias}.${prop.fixedOrderColumn}`]: QueryOrder.ASC });
1256
+ }
1257
+ if (propOrderBy) {
1258
+ for (const item of Utils.asArray(propOrderBy)) {
1259
+ for (const field of Utils.keys(item)) {
1260
+ const rawField = RawQueryFragment.getKnownFragment(field, false);
1261
+ if (rawField) {
1262
+ const sql = propAlias ? rawField.sql.replace(new RegExp(ALIAS_REPLACEMENT_RE, 'g'), propAlias) : rawField.sql;
1263
+ const raw2 = raw(sql, rawField.params);
1264
+ orderBy.push({ [raw2.toString()]: item[field] });
1265
+ continue;
1266
+ }
1267
+ orderBy.push({ [`${propAlias}.${field}`]: item[field] });
1268
+ }
1269
+ }
1270
+ }
1271
+ if (hint.children) {
1272
+ const buildJoinedPropsOrderBy = this.buildJoinedPropsOrderBy(qb, meta2, hint.children, options, path);
1273
+ orderBy.push(...buildJoinedPropsOrderBy);
1274
+ }
1275
+ }
1276
+ return orderBy;
1277
+ }
1278
+ normalizeFields(fields, prefix = '') {
1279
+ const ret = [];
1280
+ for (const field of fields) {
1281
+ if (typeof field === 'string') {
1282
+ ret.push(prefix + field);
1283
+ continue;
1284
+ }
1285
+ if (Utils.isPlainObject(field)) {
1286
+ for (const key of Object.keys(field)) {
1287
+ ret.push(...this.normalizeFields(field[key], key + '.'));
1288
+ }
1289
+ }
1290
+ }
1291
+ return ret;
1292
+ }
1293
+ processField(meta, prop, field, ret) {
1294
+ if (!prop || (prop.kind === ReferenceKind.ONE_TO_ONE && !prop.owner)) {
1295
+ return;
1296
+ }
1297
+ if (prop.kind === ReferenceKind.EMBEDDED) {
1298
+ if (prop.object) {
1299
+ ret.push(prop.name);
1300
+ return;
1301
+ }
1302
+ const parts = field.split('.');
1303
+ const top = parts.shift();
1304
+ for (const key of Object.keys(prop.embeddedProps)) {
1305
+ if (!top || key === top) {
1306
+ this.processField(meta, prop.embeddedProps[key], parts.join('.'), ret);
1307
+ }
1308
+ }
1309
+ return;
1310
+ }
1311
+ if (prop.persist === false && !prop.embedded && !prop.formula) {
1312
+ return;
1313
+ }
1314
+ ret.push(prop.name);
1315
+ }
1316
+ buildFields(meta, populate, joinedProps, qb, alias, options) {
1317
+ const lazyProps = meta.props.filter(prop => prop.lazy && !populate.some(p => this.isPopulated(meta, prop, p)));
1318
+ const hasLazyFormulas = meta.props.some(p => p.lazy && p.formula);
1319
+ const requiresSQLConversion = meta.props.some(p => p.customType?.convertToJSValueSQL && p.persist !== false);
1320
+ const hasExplicitFields = !!options.fields;
1321
+ const ret = [];
1322
+ let addFormulas = false;
1323
+ // handle root entity properties first, this is used for both strategies in the same way
1324
+ if (options.fields) {
1325
+ for (const field of this.normalizeFields(options.fields)) {
1326
+ if (field === '*') {
1327
+ ret.push('*');
1328
+ continue;
1329
+ }
1330
+ const parts = field.split('.');
1331
+ const rootPropName = parts.shift(); // first one is the `prop`
1332
+ const prop = QueryHelper.findProperty(rootPropName, {
1333
+ metadata: this.metadata,
1334
+ platform: this.platform,
1335
+ entityName: meta.className,
1336
+ where: {},
1337
+ aliasMap: qb.getAliasMap(),
1338
+ });
1339
+ this.processField(meta, prop, parts.join('.'), ret);
1340
+ }
1341
+ if (!options.fields.includes('*') && !options.fields.includes(`${qb.alias}.*`)) {
1342
+ ret.unshift(...meta.primaryKeys.filter(pk => !options.fields.includes(pk)));
1343
+ }
1344
+ if (meta.root.discriminatorColumn && !options.fields.includes(`${qb.alias}.${meta.root.discriminatorColumn}`)) {
1345
+ ret.push(meta.root.discriminatorColumn);
1346
+ }
1347
+ }
1348
+ else if (!Utils.isEmpty(options.exclude) || lazyProps.some(p => !p.formula && (p.kind !== '1:1' || p.owner))) {
1349
+ const props = meta.props.filter(prop => this.platform.shouldHaveColumn(prop, populate, options.exclude, false, false));
1350
+ ret.push(...props.filter(p => !lazyProps.includes(p)).map(p => p.name));
1351
+ addFormulas = true;
1352
+ }
1353
+ else if (hasLazyFormulas || requiresSQLConversion) {
1354
+ ret.push('*');
1355
+ addFormulas = true;
1356
+ }
1357
+ else {
1358
+ ret.push('*');
1359
+ }
1360
+ if (ret.length > 0 && !hasExplicitFields && addFormulas) {
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
+ }
1374
+ }
1375
+ // add joined relations after the root entity fields
1376
+ if (joinedProps.length > 0) {
1377
+ ret.push(...this.getFieldsForJoinedLoad(qb, meta, {
1378
+ explicitFields: options.fields,
1379
+ exclude: options.exclude,
1380
+ populate,
1381
+ parentTableAlias: alias,
1382
+ ...options,
1383
+ }));
1384
+ }
1385
+ return Utils.unique(ret);
1386
+ }
1387
+ }