@mikro-orm/knex 7.0.0-dev.7 → 7.0.0-dev.70
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.
- package/AbstractSqlConnection.d.ts +11 -5
- package/AbstractSqlConnection.js +79 -32
- package/AbstractSqlDriver.d.ts +9 -5
- package/AbstractSqlDriver.js +268 -220
- package/AbstractSqlPlatform.js +3 -3
- package/PivotCollectionPersister.d.ts +3 -2
- package/PivotCollectionPersister.js +12 -21
- package/README.md +3 -2
- package/SqlEntityManager.d.ts +9 -2
- package/SqlEntityManager.js +2 -2
- package/dialects/mssql/MsSqlNativeQueryBuilder.d.ts +2 -0
- package/dialects/mssql/MsSqlNativeQueryBuilder.js +43 -2
- package/dialects/mysql/MySqlPlatform.js +2 -1
- package/dialects/postgresql/PostgreSqlTableCompiler.d.ts +1 -0
- package/dialects/postgresql/PostgreSqlTableCompiler.js +1 -0
- package/dialects/sqlite/BaseSqliteConnection.d.ts +4 -2
- package/dialects/sqlite/BaseSqliteConnection.js +8 -5
- package/dialects/sqlite/BaseSqlitePlatform.js +1 -2
- package/dialects/sqlite/SqliteSchemaHelper.js +1 -1
- package/index.d.ts +1 -1
- package/index.js +1 -1
- package/package.json +5 -5
- package/query/ArrayCriteriaNode.d.ts +1 -0
- package/query/ArrayCriteriaNode.js +3 -0
- package/query/CriteriaNode.d.ts +4 -2
- package/query/CriteriaNode.js +11 -6
- package/query/CriteriaNodeFactory.js +12 -7
- package/query/NativeQueryBuilder.js +1 -1
- package/query/ObjectCriteriaNode.d.ts +1 -0
- package/query/ObjectCriteriaNode.js +38 -9
- package/query/QueryBuilder.d.ts +59 -7
- package/query/QueryBuilder.js +171 -47
- package/query/QueryBuilderHelper.d.ts +1 -1
- package/query/QueryBuilderHelper.js +15 -8
- package/query/ScalarCriteriaNode.d.ts +3 -3
- package/query/ScalarCriteriaNode.js +9 -7
- package/query/index.d.ts +1 -0
- package/query/index.js +1 -0
- package/query/raw.d.ts +59 -0
- package/query/raw.js +68 -0
- package/query/rawKnex.d.ts +58 -0
- package/query/rawKnex.js +72 -0
- package/schema/DatabaseSchema.js +25 -4
- package/schema/DatabaseTable.d.ts +5 -4
- package/schema/DatabaseTable.js +67 -33
- package/schema/SchemaComparator.js +2 -2
- package/schema/SchemaHelper.d.ts +2 -0
- package/schema/SchemaHelper.js +8 -4
- package/schema/SqlSchemaGenerator.d.ts +13 -6
- package/schema/SqlSchemaGenerator.js +38 -17
- package/typings.d.ts +85 -3
package/AbstractSqlDriver.js
CHANGED
|
@@ -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
|
|
25
|
-
|
|
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(
|
|
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 (
|
|
75
|
+
if (options.last && !options.first) {
|
|
71
76
|
result.reverse();
|
|
72
77
|
}
|
|
73
78
|
return result;
|
|
@@ -127,35 +132,49 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
127
132
|
const expr = this.platform.formatQuery(res.sql, res.params);
|
|
128
133
|
return this.wrapVirtualExpressionInSubquery(meta, expr, where, options, type);
|
|
129
134
|
}
|
|
130
|
-
/* v8 ignore next */
|
|
135
|
+
/* v8 ignore next 2 */
|
|
131
136
|
return res;
|
|
132
137
|
}
|
|
133
|
-
async
|
|
134
|
-
const
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
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 3 */
|
|
141
|
+
if (!meta.expression) {
|
|
142
|
+
return;
|
|
152
143
|
}
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
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;
|
|
156
154
|
}
|
|
157
|
-
|
|
158
|
-
|
|
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 2 */
|
|
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,11 +182,23 @@ 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
204
|
/* v8 ignore next 3 */
|
|
@@ -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.
|
|
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,12 +268,16 @@ 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
282
|
/* v8 ignore next 3 */
|
|
244
283
|
if (prop.fieldNames.length > 1) { // composite keys
|
|
@@ -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
|
-
|
|
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.
|
|
316
|
-
if (meta
|
|
352
|
+
const meta = this.metadata.get(entityName);
|
|
353
|
+
if (meta.virtual) {
|
|
317
354
|
return this.countVirtual(entityName, where, options);
|
|
318
355
|
}
|
|
319
|
-
|
|
320
|
-
const
|
|
321
|
-
const
|
|
322
|
-
const qb = this.createQueryBuilder(entityName, options.ctx, options.connectionType, false, options.logging)
|
|
323
|
-
|
|
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 (
|
|
332
|
-
|
|
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
|
-
|
|
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,7 +512,7 @@ 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
517
|
/* v8 ignore next 3 */
|
|
471
518
|
if (pks.length > 1) { // owner has composite pk
|
|
@@ -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
|
/**
|
|
@@ -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
|
-
|
|
735
|
-
|
|
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
|
-
[
|
|
810
|
+
[pivotProp2.name]: { $in: ownerMeta.compositePK ? owners : owners.map(o => o[0]) },
|
|
763
811
|
};
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
}
|
|
771
|
-
|
|
772
|
-
const
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
const
|
|
776
|
-
|
|
777
|
-
...
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
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(
|
|
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
|
|
831
|
-
const key = Utils.getPrimaryKeyHash(
|
|
832
|
-
|
|
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,
|
|
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
|
|
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
|
|
856
|
+
return Utils.asArray(prop.orderBy).map(o => ({ [pivotProp.name]: o }));
|
|
850
857
|
}
|
|
851
858
|
if (prop.fixedOrder) {
|
|
852
|
-
return [{ [
|
|
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:
|
|
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
|
-
|
|
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 (
|
|
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
|
|
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
|
-
|
|
910
|
-
|
|
911
|
-
|
|
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 &&
|
|
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.
|
|
974
|
-
?
|
|
975
|
-
:
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
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
|
}
|