@mikro-orm/knex 7.0.0-dev.6 → 7.0.0-dev.60
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 +14 -10
- package/AbstractSqlDriver.js +273 -222
- package/AbstractSqlPlatform.js +3 -3
- package/PivotCollectionPersister.d.ts +3 -2
- package/PivotCollectionPersister.js +6 -2
- 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/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 +11 -6
- package/query/NativeQueryBuilder.js +1 -1
- package/query/ObjectCriteriaNode.d.ts +1 -0
- package/query/ObjectCriteriaNode.js +38 -7
- package/query/QueryBuilder.d.ts +67 -6
- package/query/QueryBuilder.js +195 -43
- package/query/QueryBuilderHelper.d.ts +1 -1
- package/query/QueryBuilderHelper.js +13 -6
- package/query/ScalarCriteriaNode.d.ts +3 -3
- package/query/ScalarCriteriaNode.js +7 -5
- 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 +80 -34
- package/schema/SchemaComparator.js +2 -2
- package/schema/SchemaHelper.d.ts +2 -0
- package/schema/SchemaHelper.js +8 -4
- package/schema/SqlSchemaGenerator.d.ts +9 -2
- package/schema/SqlSchemaGenerator.js +31 -11
- package/typings.d.ts +86 -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);
|
|
@@ -63,8 +59,20 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
63
59
|
if (options.lockMode) {
|
|
64
60
|
qb.setLockMode(options.lockMode, options.lockTableAliases);
|
|
65
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);
|
|
66
74
|
const result = await this.rethrow(qb.execute('all'));
|
|
67
|
-
if (
|
|
75
|
+
if (options.last && !options.first) {
|
|
68
76
|
result.reverse();
|
|
69
77
|
}
|
|
70
78
|
return result;
|
|
@@ -124,35 +132,49 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
124
132
|
const expr = this.platform.formatQuery(res.sql, res.params);
|
|
125
133
|
return this.wrapVirtualExpressionInSubquery(meta, expr, where, options, type);
|
|
126
134
|
}
|
|
127
|
-
/* v8 ignore next */
|
|
135
|
+
/* v8 ignore next 2 */
|
|
128
136
|
return res;
|
|
129
137
|
}
|
|
130
|
-
async
|
|
131
|
-
const
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
qb.where(where);
|
|
136
|
-
const { first, last, before, after } = options;
|
|
137
|
-
const isCursorPagination = [first, last, before, after].some(v => v != null);
|
|
138
|
-
if (type !== QueryType.COUNT) {
|
|
139
|
-
if (options.orderBy) {
|
|
140
|
-
if (isCursorPagination) {
|
|
141
|
-
const { orderBy: newOrderBy, where } = this.processCursorOptions(meta, options, options.orderBy);
|
|
142
|
-
qb.andWhere(where).orderBy(newOrderBy);
|
|
143
|
-
}
|
|
144
|
-
else {
|
|
145
|
-
qb.orderBy(options.orderBy);
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
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;
|
|
149
143
|
}
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
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;
|
|
153
154
|
}
|
|
154
|
-
|
|
155
|
-
|
|
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();
|
|
156
178
|
}
|
|
157
179
|
native.from(raw(`(${expression}) as ${this.platform.quoteIdentifier(qb.alias)}`));
|
|
158
180
|
const query = native.compile();
|
|
@@ -160,11 +182,23 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
160
182
|
if (type === QueryType.COUNT) {
|
|
161
183
|
return res[0].count;
|
|
162
184
|
}
|
|
163
|
-
if (isCursorPagination && !first && !!last) {
|
|
185
|
+
if (isCursorPagination && !options.first && !!options.last) {
|
|
164
186
|
res.reverse();
|
|
165
187
|
}
|
|
166
188
|
return res.map(row => this.mapResult(row, meta));
|
|
167
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
|
+
}
|
|
168
202
|
mapResult(result, meta, populate = [], qb, map = {}) {
|
|
169
203
|
const ret = super.mapResult(result, meta);
|
|
170
204
|
/* v8 ignore next 3 */
|
|
@@ -218,9 +252,13 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
218
252
|
}
|
|
219
253
|
return;
|
|
220
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 || []));
|
|
221
259
|
// If the primary key value for the relation is null, we know we haven't joined to anything
|
|
222
260
|
// and therefore we don't return any record (since all values would be null)
|
|
223
|
-
const hasPK = meta2.
|
|
261
|
+
const hasPK = meta2.getPrimaryProps().every(pk => pk.fieldNames.every(name => {
|
|
224
262
|
return root[`${relationAlias}__${name}`] != null;
|
|
225
263
|
}));
|
|
226
264
|
if (!hasPK) {
|
|
@@ -230,12 +268,16 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
230
268
|
if ([ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind)) {
|
|
231
269
|
result[prop.name] = null;
|
|
232
270
|
}
|
|
271
|
+
for (const prop of targetProps) {
|
|
272
|
+
for (const name of prop.fieldNames) {
|
|
273
|
+
delete root[`${relationAlias}__${name}`];
|
|
274
|
+
}
|
|
275
|
+
}
|
|
233
276
|
return;
|
|
234
277
|
}
|
|
235
278
|
let relationPojo = {};
|
|
236
279
|
meta2.props
|
|
237
280
|
.filter(prop => !ref && prop.persist === false && prop.fieldNames)
|
|
238
|
-
.filter(prop => !prop.lazy || populate.some(p => p.field === prop.name || p.all))
|
|
239
281
|
.forEach(prop => {
|
|
240
282
|
/* v8 ignore next 3 */
|
|
241
283
|
if (prop.fieldNames.length > 1) { // composite keys
|
|
@@ -246,10 +288,6 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
246
288
|
relationPojo[prop.name] = root[alias];
|
|
247
289
|
}
|
|
248
290
|
});
|
|
249
|
-
const mapToPk = !!(ref || prop.mapToPk);
|
|
250
|
-
const targetProps = mapToPk
|
|
251
|
-
? meta2.getPrimaryProps()
|
|
252
|
-
: meta2.props.filter(prop => this.platform.shouldHaveColumn(prop, hint.children || []));
|
|
253
291
|
const tz = this.platform.getTimezone();
|
|
254
292
|
for (const prop of targetProps) {
|
|
255
293
|
if (prop.fieldNames.every(name => typeof root[`${relationAlias}__${name}`] === 'undefined')) {
|
|
@@ -290,7 +328,9 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
290
328
|
// properties can be mapped to multiple places, e.g. when sharing a column in multiple FKs,
|
|
291
329
|
// so we need to delete them after everything is mapped from given level
|
|
292
330
|
for (const prop of targetProps) {
|
|
293
|
-
|
|
331
|
+
for (const name of prop.fieldNames) {
|
|
332
|
+
delete root[`${relationAlias}__${name}`];
|
|
333
|
+
}
|
|
294
334
|
}
|
|
295
335
|
if (mapToPk) {
|
|
296
336
|
const tmp = Object.values(relationPojo);
|
|
@@ -309,15 +349,20 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
309
349
|
});
|
|
310
350
|
}
|
|
311
351
|
async count(entityName, where, options = {}) {
|
|
312
|
-
const meta = this.metadata.
|
|
313
|
-
if (meta
|
|
352
|
+
const meta = this.metadata.get(entityName);
|
|
353
|
+
if (meta.virtual) {
|
|
314
354
|
return this.countVirtual(entityName, where, options);
|
|
315
355
|
}
|
|
316
|
-
|
|
317
|
-
const
|
|
318
|
-
const
|
|
319
|
-
const qb = this.createQueryBuilder(entityName, options.ctx, options.connectionType, false, options.logging)
|
|
320
|
-
|
|
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)
|
|
321
366
|
.comment(options.comments)
|
|
322
367
|
.hintComment(options.hintComments)
|
|
323
368
|
.groupBy(options.groupBy)
|
|
@@ -325,8 +370,8 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
325
370
|
.populate(populate, joinedProps.length > 0 ? populateWhere : undefined, joinedProps.length > 0 ? options.populateFilter : undefined)
|
|
326
371
|
.withSchema(this.getSchemaName(meta, options))
|
|
327
372
|
.where(where);
|
|
328
|
-
if (
|
|
329
|
-
|
|
373
|
+
if (options.em) {
|
|
374
|
+
await qb.applyJoinedFilters(options.em, options.filters);
|
|
330
375
|
}
|
|
331
376
|
return this.rethrow(qb.getCount());
|
|
332
377
|
}
|
|
@@ -335,7 +380,7 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
335
380
|
const meta = this.metadata.find(entityName);
|
|
336
381
|
const collections = this.extractManyToMany(entityName, data);
|
|
337
382
|
const pks = meta?.primaryKeys ?? [this.config.getNamingStrategy().referenceColumnName()];
|
|
338
|
-
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));
|
|
339
384
|
const res = await this.rethrow(qb.insert(data).execute('run', false));
|
|
340
385
|
res.row = res.row || {};
|
|
341
386
|
let pk;
|
|
@@ -383,7 +428,12 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
383
428
|
sql += ' ' + data.map(() => `select null as ${this.platform.quoteIdentifier(pks[0])}`).join(' union all ');
|
|
384
429
|
}
|
|
385
430
|
const addParams = (prop, row) => {
|
|
386
|
-
|
|
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;
|
|
387
437
|
if (prop.kind === ReferenceKind.EMBEDDED && prop.object) {
|
|
388
438
|
if (prop.array && value) {
|
|
389
439
|
value = this.platform.cloneEmbeddable(value);
|
|
@@ -396,14 +446,14 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
396
446
|
value = this.mapDataToFieldNames(value, false, prop.embeddedProps, options.convertCustomTypes);
|
|
397
447
|
}
|
|
398
448
|
}
|
|
399
|
-
if (options.convertCustomTypes && prop.customType) {
|
|
400
|
-
params.push(prop.customType.convertToDatabaseValue(value, this.platform, { key: prop.name, mode: 'query-data' }));
|
|
401
|
-
return;
|
|
402
|
-
}
|
|
403
449
|
if (typeof value === 'undefined' && this.platform.usesDefaultKeyword()) {
|
|
404
450
|
params.push(raw('default'));
|
|
405
451
|
return;
|
|
406
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
|
+
}
|
|
407
457
|
params.push(value);
|
|
408
458
|
};
|
|
409
459
|
if (fields.length > 0 || this.platform.usesDefaultKeyword()) {
|
|
@@ -437,7 +487,7 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
437
487
|
else {
|
|
438
488
|
const field = prop.fieldNames[0];
|
|
439
489
|
if (!duplicates.includes(field) || !usedDups.includes(field)) {
|
|
440
|
-
if (prop.customType && !prop.object && 'convertToDatabaseValueSQL' in prop.customType && !isRaw(row[prop.name])) {
|
|
490
|
+
if (prop.customType && !prop.object && 'convertToDatabaseValueSQL' in prop.customType && row[prop.name] != null && !isRaw(row[prop.name])) {
|
|
441
491
|
keys.push(prop.customType.convertToDatabaseValueSQL('?', this.platform));
|
|
442
492
|
}
|
|
443
493
|
else {
|
|
@@ -462,7 +512,7 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
462
512
|
if (transform) {
|
|
463
513
|
sql = transform(sql);
|
|
464
514
|
}
|
|
465
|
-
const res = await this.execute(sql, params, 'run', options.ctx);
|
|
515
|
+
const res = await this.execute(sql, params, 'run', options.ctx, options.loggerContext);
|
|
466
516
|
let pk;
|
|
467
517
|
/* v8 ignore next 3 */
|
|
468
518
|
if (pks.length > 1) { // owner has composite pk
|
|
@@ -490,7 +540,7 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
490
540
|
where = { [meta?.primaryKeys[0] ?? pks[0]]: where };
|
|
491
541
|
}
|
|
492
542
|
if (Utils.hasObjectKeys(data)) {
|
|
493
|
-
const qb = this.createQueryBuilder(entityName, options.ctx, 'write', options.convertCustomTypes)
|
|
543
|
+
const qb = this.createQueryBuilder(entityName, options.ctx, 'write', options.convertCustomTypes, options.loggerContext)
|
|
494
544
|
.withSchema(this.getSchemaName(meta, options));
|
|
495
545
|
if (options.upsert) {
|
|
496
546
|
/* v8 ignore next */
|
|
@@ -529,7 +579,7 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
529
579
|
const meta = this.metadata.get(entityName);
|
|
530
580
|
if (options.upsert) {
|
|
531
581
|
const uniqueFields = options.onConflictFields ?? (Utils.isPlainObject(where[0]) ? Object.keys(where[0]).flatMap(key => Utils.splitPrimaryKeys(key)) : meta.primaryKeys);
|
|
532
|
-
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));
|
|
533
583
|
const returning = getOnConflictReturningFields(meta, data[0], uniqueFields, options);
|
|
534
584
|
qb.insert(data)
|
|
535
585
|
.onConflict(uniqueFields)
|
|
@@ -588,7 +638,7 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
588
638
|
if (key in data[idx]) {
|
|
589
639
|
const pks = Utils.getOrderedPrimaryKeys(cond, meta);
|
|
590
640
|
sql += ` when (${pkCond}) then `;
|
|
591
|
-
if (prop.customType && !prop.object && 'convertToDatabaseValueSQL' in prop.customType && !isRaw(data[idx][key])) {
|
|
641
|
+
if (prop.customType && !prop.object && 'convertToDatabaseValueSQL' in prop.customType && data[idx][prop.name] != null && !isRaw(data[idx][key])) {
|
|
592
642
|
sql += prop.customType.convertToDatabaseValueSQL('?', this.platform);
|
|
593
643
|
}
|
|
594
644
|
else {
|
|
@@ -642,7 +692,7 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
642
692
|
/* v8 ignore next */
|
|
643
693
|
sql += returningFields.length > 0 ? ` returning ${returningFields.map(field => this.platform.quoteIdentifier(field)).join(', ')}` : '';
|
|
644
694
|
}
|
|
645
|
-
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));
|
|
646
696
|
for (let i = 0; i < collections.length; i++) {
|
|
647
697
|
await this.processManyToMany(meta, where[i], collections[i], false, options);
|
|
648
698
|
}
|
|
@@ -654,7 +704,7 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
654
704
|
if (Utils.isPrimaryKey(where) && pks.length === 1) {
|
|
655
705
|
where = { [pks[0]]: where };
|
|
656
706
|
}
|
|
657
|
-
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));
|
|
658
708
|
return this.rethrow(qb.execute('run', false));
|
|
659
709
|
}
|
|
660
710
|
/**
|
|
@@ -728,14 +778,20 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
728
778
|
const pivotMeta = this.metadata.find(coll.property.pivotEntity);
|
|
729
779
|
let schema = pivotMeta.schema;
|
|
730
780
|
if (schema === '*') {
|
|
731
|
-
|
|
732
|
-
|
|
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
|
+
}
|
|
733
789
|
}
|
|
734
790
|
else if (schema == null) {
|
|
735
791
|
schema = this.config.get('schema');
|
|
736
792
|
}
|
|
737
793
|
const tableName = `${schema ?? '_'}.${pivotMeta.tableName}`;
|
|
738
|
-
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);
|
|
739
795
|
persister.enqueueUpdate(coll.property, insertDiff, deleteDiff, pks);
|
|
740
796
|
}
|
|
741
797
|
for (const persister of Utils.values(groups)) {
|
|
@@ -743,116 +799,88 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
743
799
|
}
|
|
744
800
|
}
|
|
745
801
|
async loadFromPivotTable(prop, owners, where = {}, orderBy, ctx, options, pivotJoin) {
|
|
802
|
+
if (owners.length === 0) {
|
|
803
|
+
return {};
|
|
804
|
+
}
|
|
746
805
|
const pivotMeta = this.metadata.find(prop.pivotEntity);
|
|
747
806
|
const pivotProp1 = pivotMeta.relations[prop.owner ? 1 : 0];
|
|
748
807
|
const pivotProp2 = pivotMeta.relations[prop.owner ? 0 : 1];
|
|
749
808
|
const ownerMeta = this.metadata.find(pivotProp2.type);
|
|
750
|
-
options = { ...options };
|
|
751
|
-
const qb = this.createQueryBuilder(prop.pivotEntity, ctx, options.connectionType, undefined, options?.logging)
|
|
752
|
-
.withSchema(this.getSchemaName(pivotMeta, options))
|
|
753
|
-
.indexHint(options.indexHint)
|
|
754
|
-
.comment(options.comments)
|
|
755
|
-
.hintComment(options.hintComments);
|
|
756
|
-
const pivotAlias = qb.alias;
|
|
757
|
-
const pivotKey = pivotProp2.joinColumns.map(column => `${pivotAlias}.${column}`).join(Utils.PK_SEPARATOR);
|
|
758
809
|
const cond = {
|
|
759
|
-
[
|
|
810
|
+
[pivotProp2.name]: { $in: ownerMeta.compositePK ? owners : owners.map(o => o[0]) },
|
|
760
811
|
};
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
}
|
|
768
|
-
|
|
769
|
-
const
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
const
|
|
773
|
-
|
|
774
|
-
...
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
for (const field of targetFields) {
|
|
784
|
-
const f = field.toString();
|
|
785
|
-
fields.unshift(f.includes('.') ? field : `${targetAlias}.${f}`);
|
|
786
|
-
if (RawQueryFragment.isKnownFragment(field)) {
|
|
787
|
-
qb.rawFragments.add(f);
|
|
788
|
-
}
|
|
789
|
-
}
|
|
790
|
-
// we need to handle 1:1 owner auto-joins explicitly, as the QB type is the pivot table, not the target
|
|
791
|
-
populate.forEach(hint => {
|
|
792
|
-
const alias = qb.getNextAlias(prop.targetMeta.tableName);
|
|
793
|
-
qb.leftJoin(`${targetAlias}.${hint.field}`, alias);
|
|
794
|
-
// eslint-disable-next-line dot-notation
|
|
795
|
-
for (const join of Object.values(qb['_joins'])) {
|
|
796
|
-
const [propName] = hint.field.split(':', 2);
|
|
797
|
-
if (join.alias === alias && join.prop.name === propName) {
|
|
798
|
-
fields.push(...qb.helper.mapJoinColumns(qb.type, join));
|
|
799
|
-
}
|
|
800
|
-
}
|
|
801
|
-
});
|
|
802
|
-
}
|
|
803
|
-
qb.select(fields)
|
|
804
|
-
.where({ [pivotProp1.name]: where })
|
|
805
|
-
.orderBy(orderBy)
|
|
806
|
-
.setLockMode(options.lockMode, options.lockTableAliases);
|
|
807
|
-
if (owners.length === 1 && (options.offset != null || options.limit != null)) {
|
|
808
|
-
qb.limit(options.limit, options.offset);
|
|
809
|
-
}
|
|
810
|
-
const res = owners.length ? await this.rethrow(qb.execute('all', { mergeResults: false, mapResults: false })) : [];
|
|
811
|
-
const tmp = {};
|
|
812
|
-
const items = res.map((row) => {
|
|
813
|
-
const root = super.mapResult(row, prop.targetMeta);
|
|
814
|
-
this.mapJoinedProps(root, prop.targetMeta, populate, qb, root, tmp, pivotMeta.className + '.' + pivotProp1.name);
|
|
815
|
-
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,
|
|
816
834
|
});
|
|
817
|
-
qb.clearRawFragmentsCache();
|
|
818
835
|
const map = {};
|
|
819
|
-
const pkProps = ownerMeta.getPrimaryProps();
|
|
820
836
|
for (const owner of owners) {
|
|
821
|
-
const key = Utils.getPrimaryKeyHash(
|
|
822
|
-
const pkProp = pkProps[idx];
|
|
823
|
-
return pkProp.customType ? pkProp.customType.convertToJSValue(owner[idx], this.platform) : owner[idx];
|
|
824
|
-
}));
|
|
837
|
+
const key = Utils.getPrimaryKeyHash(owner);
|
|
825
838
|
map[key] = [];
|
|
826
839
|
}
|
|
827
|
-
for (const item of
|
|
828
|
-
const key = Utils.getPrimaryKeyHash(
|
|
829
|
-
|
|
830
|
-
return pkProp.customType ? pkProp.customType.convertToJSValue(item[`fk__${col}`], this.platform) : item[`fk__${col}`];
|
|
831
|
-
}));
|
|
832
|
-
map[key].push(item);
|
|
833
|
-
prop.joinColumns.forEach(col => delete item[`fk__${col}`]);
|
|
834
|
-
prop.inverseJoinColumns.forEach((col, idx) => {
|
|
835
|
-
Utils.renameKey(item, `fk__${col}`, prop.targetMeta.primaryKeys[idx]);
|
|
836
|
-
});
|
|
840
|
+
for (const item of res) {
|
|
841
|
+
const key = Utils.getPrimaryKeyHash(Utils.asArray(item[pivotProp2.name]));
|
|
842
|
+
map[key].push(item[pivotProp1.name]);
|
|
837
843
|
}
|
|
838
844
|
return map;
|
|
839
845
|
}
|
|
840
|
-
getPivotOrderBy(prop, pivotProp,
|
|
841
|
-
// FIXME this is ignoring the rest of the array items
|
|
846
|
+
getPivotOrderBy(prop, pivotProp, orderBy, parentOrderBy) {
|
|
842
847
|
if (!Utils.isEmpty(orderBy)) {
|
|
843
|
-
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] }));
|
|
844
854
|
}
|
|
845
855
|
if (!Utils.isEmpty(prop.orderBy)) {
|
|
846
|
-
return
|
|
856
|
+
return Utils.asArray(prop.orderBy).map(o => ({ [pivotProp.name]: o }));
|
|
847
857
|
}
|
|
848
858
|
if (prop.fixedOrder) {
|
|
849
|
-
return [{ [
|
|
859
|
+
return [{ [prop.fixedOrderColumn]: QueryOrder.ASC }];
|
|
850
860
|
}
|
|
851
861
|
return [];
|
|
852
862
|
}
|
|
853
863
|
async execute(query, params = [], method = 'all', ctx, loggerContext) {
|
|
854
864
|
return this.rethrow(this.connection.execute(query, params, method, ctx, loggerContext));
|
|
855
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
|
+
}
|
|
856
884
|
/**
|
|
857
885
|
* 1:1 owner side needs to be marked for population so QB auto-joins the owner id
|
|
858
886
|
*/
|
|
@@ -864,7 +892,7 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
864
892
|
const toPopulate = meta.relations
|
|
865
893
|
.filter(prop => prop.kind === ReferenceKind.ONE_TO_ONE && !prop.owner && !prop.lazy && !relationsToPopulate.includes(prop.name))
|
|
866
894
|
.filter(prop => fields.length === 0 || fields.some(f => prop.name === f || prop.name.startsWith(`${String(f)}.`)))
|
|
867
|
-
.map(prop => ({ field: `${prop.name}:ref`, strategy:
|
|
895
|
+
.map(prop => ({ field: `${prop.name}:ref`, strategy: LoadStrategy.JOINED }));
|
|
868
896
|
return [...populate, ...toPopulate];
|
|
869
897
|
}
|
|
870
898
|
/**
|
|
@@ -874,14 +902,15 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
874
902
|
return populate.filter(hint => {
|
|
875
903
|
const [propName, ref] = hint.field.split(':', 2);
|
|
876
904
|
const prop = meta.properties[propName] || {};
|
|
877
|
-
|
|
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)) {
|
|
878
907
|
return true;
|
|
879
908
|
}
|
|
880
909
|
// skip redundant joins for 1:1 owner population hints when using `mapToPk`
|
|
881
910
|
if (prop.kind === ReferenceKind.ONE_TO_ONE && prop.mapToPk && prop.owner) {
|
|
882
911
|
return false;
|
|
883
912
|
}
|
|
884
|
-
if (
|
|
913
|
+
if (strategy !== LoadStrategy.JOINED) {
|
|
885
914
|
// force joined strategy for explicit 1:1 owner populate hint as it would require a join anyway
|
|
886
915
|
return prop.kind === ReferenceKind.ONE_TO_ONE && !prop.owner;
|
|
887
916
|
}
|
|
@@ -892,31 +921,26 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
892
921
|
* @internal
|
|
893
922
|
*/
|
|
894
923
|
mergeJoinedResult(rawResults, meta, joinedProps) {
|
|
924
|
+
if (rawResults.length <= 1) {
|
|
925
|
+
return rawResults;
|
|
926
|
+
}
|
|
895
927
|
const res = [];
|
|
896
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
|
+
});
|
|
897
934
|
for (const item of rawResults) {
|
|
898
935
|
const pk = Utils.getCompositeKeyHash(item, meta);
|
|
899
936
|
if (map[pk]) {
|
|
900
|
-
for (const
|
|
901
|
-
const [propName, ref] = hint.field.split(':', 2);
|
|
902
|
-
const prop = meta.properties[propName];
|
|
937
|
+
for (const { propName } of hints) {
|
|
903
938
|
if (!item[propName]) {
|
|
904
939
|
continue;
|
|
905
940
|
}
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
}
|
|
910
|
-
switch (prop.kind) {
|
|
911
|
-
case ReferenceKind.ONE_TO_MANY:
|
|
912
|
-
case ReferenceKind.MANY_TO_MANY:
|
|
913
|
-
map[pk][propName] = this.mergeJoinedResult([...map[pk][propName], ...item[propName]], prop.targetMeta, hint.children ?? []);
|
|
914
|
-
break;
|
|
915
|
-
case ReferenceKind.MANY_TO_ONE:
|
|
916
|
-
case ReferenceKind.ONE_TO_ONE:
|
|
917
|
-
map[pk][propName] = this.mergeJoinedResult([map[pk][propName], item[propName]], prop.targetMeta, hint.children ?? [])[0];
|
|
918
|
-
break;
|
|
919
|
-
}
|
|
941
|
+
collectionsToMerge[pk] ??= {};
|
|
942
|
+
collectionsToMerge[pk][propName] ??= [map[pk][propName]];
|
|
943
|
+
collectionsToMerge[pk][propName].push(item[propName]);
|
|
920
944
|
}
|
|
921
945
|
}
|
|
922
946
|
else {
|
|
@@ -924,37 +948,59 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
924
948
|
res.push(item);
|
|
925
949
|
}
|
|
926
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
|
+
}
|
|
927
976
|
return res;
|
|
928
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
|
+
}
|
|
929
987
|
getFieldsForJoinedLoad(qb, meta, options) {
|
|
930
988
|
const fields = [];
|
|
931
989
|
const populate = options.populate ?? [];
|
|
932
990
|
const joinedProps = this.joinedProps(meta, populate, options);
|
|
933
|
-
const shouldHaveColumn = (meta, prop, populate, fields) => {
|
|
934
|
-
if (!this.platform.shouldHaveColumn(prop, populate, options.exclude)) {
|
|
935
|
-
return false;
|
|
936
|
-
}
|
|
937
|
-
if (!fields || fields.includes('*') || prop.primary || meta.root.discriminatorColumn === prop.name) {
|
|
938
|
-
return true;
|
|
939
|
-
}
|
|
940
|
-
return fields.some(f => f === prop.name || f.toString().startsWith(prop.name + '.'));
|
|
941
|
-
};
|
|
942
991
|
const populateWhereAll = options?._populateWhere === 'all' || Utils.isEmpty(options?._populateWhere);
|
|
943
992
|
// root entity is already handled, skip that
|
|
944
993
|
if (options.parentJoinPath) {
|
|
945
994
|
// alias all fields in the primary table
|
|
946
995
|
meta.props
|
|
947
|
-
.filter(prop => shouldHaveColumn(meta, prop, populate, options.explicitFields))
|
|
948
|
-
.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)));
|
|
949
998
|
}
|
|
950
999
|
for (const hint of joinedProps) {
|
|
951
1000
|
const [propName, ref] = hint.field.split(':', 2);
|
|
952
1001
|
const prop = meta.properties[propName];
|
|
953
1002
|
// ignore ref joins of known FKs unless it's a filter hint
|
|
954
|
-
if (ref && !hint.filter && (prop.kind === ReferenceKind.MANY_TO_ONE || (prop.kind === ReferenceKind.ONE_TO_ONE &&
|
|
955
|
-
continue;
|
|
956
|
-
}
|
|
957
|
-
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))) {
|
|
958
1004
|
continue;
|
|
959
1005
|
}
|
|
960
1006
|
const meta2 = this.metadata.find(prop.type);
|
|
@@ -965,11 +1011,14 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
965
1011
|
if (!options.parentJoinPath && populateWhereAll && !hint.filter && !path.startsWith('[populate]')) {
|
|
966
1012
|
path = '[populate]' + path;
|
|
967
1013
|
}
|
|
1014
|
+
const mandatoryToOneProperty = [ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind) && !prop.nullable;
|
|
968
1015
|
const joinType = pivotRefJoin
|
|
969
1016
|
? JoinType.pivotJoin
|
|
970
|
-
: hint.
|
|
971
|
-
?
|
|
972
|
-
:
|
|
1017
|
+
: hint.joinType
|
|
1018
|
+
? hint.joinType
|
|
1019
|
+
: (hint.filter && !prop.nullable) || mandatoryToOneProperty
|
|
1020
|
+
? JoinType.innerJoin
|
|
1021
|
+
: JoinType.leftJoin;
|
|
973
1022
|
qb.join(field, tableAlias, {}, joinType, path);
|
|
974
1023
|
if (pivotRefJoin) {
|
|
975
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}`)));
|
|
@@ -1001,7 +1050,7 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
1001
1050
|
parentJoinPath: path,
|
|
1002
1051
|
}));
|
|
1003
1052
|
}
|
|
1004
|
-
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))) {
|
|
1005
1054
|
fields.push(...prop.referencedColumnNames.map(col => qb.helper.mapper(`${tableAlias}.${col}`, qb.type, undefined, `${tableAlias}__${col}`)));
|
|
1006
1055
|
}
|
|
1007
1056
|
}
|
|
@@ -1010,9 +1059,18 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
1010
1059
|
/**
|
|
1011
1060
|
* @internal
|
|
1012
1061
|
*/
|
|
1013
|
-
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
|
+
}
|
|
1014
1072
|
const aliased = this.platform.quoteIdentifier(`${tableAlias}__${prop.fieldNames[0]}`);
|
|
1015
|
-
if (prop.customTypes?.some(type => type?.convertToJSValueSQL)) {
|
|
1073
|
+
if (prop.customTypes?.some(type => !!type?.convertToJSValueSQL)) {
|
|
1016
1074
|
return prop.fieldNames.map((col, idx) => {
|
|
1017
1075
|
if (!prop.customTypes[idx]?.convertToJSValueSQL) {
|
|
1018
1076
|
return col;
|
|
@@ -1076,7 +1134,7 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
1076
1134
|
for (const prop of meta.relations) {
|
|
1077
1135
|
if (collections[prop.name]) {
|
|
1078
1136
|
const pivotMeta = this.metadata.find(prop.pivotEntity);
|
|
1079
|
-
const persister = new PivotCollectionPersister(pivotMeta, this, options?.ctx, options?.schema);
|
|
1137
|
+
const persister = new PivotCollectionPersister(pivotMeta, this, options?.ctx, options?.schema, options?.loggerContext);
|
|
1080
1138
|
persister.enqueueUpdate(prop, collections[prop.name], clear, pks);
|
|
1081
1139
|
await this.rethrow(persister.execute());
|
|
1082
1140
|
}
|
|
@@ -1143,7 +1201,7 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
1143
1201
|
let path = parentPath;
|
|
1144
1202
|
const meta2 = this.metadata.find(prop.type);
|
|
1145
1203
|
const childOrder = orderHint[prop.name];
|
|
1146
|
-
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))) {
|
|
1147
1205
|
path += `.${propName}`;
|
|
1148
1206
|
}
|
|
1149
1207
|
if (prop.kind === ReferenceKind.MANY_TO_MANY && typeof childOrder !== 'object') {
|
|
@@ -1151,10 +1209,10 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
1151
1209
|
}
|
|
1152
1210
|
const join = qb.getJoinForPath(path, { matchPopulateJoins: true });
|
|
1153
1211
|
const propAlias = qb.getAliasForJoinPath(join ?? path, { matchPopulateJoins: true }) ?? parentAlias;
|
|
1154
|
-
if (!join
|
|
1212
|
+
if (!join) {
|
|
1155
1213
|
continue;
|
|
1156
1214
|
}
|
|
1157
|
-
if (![ReferenceKind.SCALAR, ReferenceKind.EMBEDDED].includes(prop.kind) && typeof childOrder === 'object') {
|
|
1215
|
+
if (join && ![ReferenceKind.SCALAR, ReferenceKind.EMBEDDED].includes(prop.kind) && typeof childOrder === 'object') {
|
|
1158
1216
|
const children = this.buildPopulateOrderBy(qb, meta2, Utils.asArray(childOrder), path, explicit, propAlias);
|
|
1159
1217
|
orderBy.push(...children);
|
|
1160
1218
|
continue;
|
|
@@ -1232,7 +1290,7 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
1232
1290
|
}
|
|
1233
1291
|
return ret;
|
|
1234
1292
|
}
|
|
1235
|
-
processField(meta, prop, field, ret
|
|
1293
|
+
processField(meta, prop, field, ret) {
|
|
1236
1294
|
if (!prop || (prop.kind === ReferenceKind.ONE_TO_ONE && !prop.owner)) {
|
|
1237
1295
|
return;
|
|
1238
1296
|
}
|
|
@@ -1245,7 +1303,7 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
1245
1303
|
const top = parts.shift();
|
|
1246
1304
|
for (const key of Object.keys(prop.embeddedProps)) {
|
|
1247
1305
|
if (!top || key === top) {
|
|
1248
|
-
this.processField(meta, prop.embeddedProps[key], parts.join('.'), ret
|
|
1306
|
+
this.processField(meta, prop.embeddedProps[key], parts.join('.'), ret);
|
|
1249
1307
|
}
|
|
1250
1308
|
}
|
|
1251
1309
|
return;
|
|
@@ -1255,16 +1313,7 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
1255
1313
|
}
|
|
1256
1314
|
ret.push(prop.name);
|
|
1257
1315
|
}
|
|
1258
|
-
|
|
1259
|
-
if (hint.field === prop.name || hint.field === name || hint.all) {
|
|
1260
|
-
return true;
|
|
1261
|
-
}
|
|
1262
|
-
if (prop.embedded && hint.children && meta.properties[prop.embedded[0]].name === hint.field) {
|
|
1263
|
-
return hint.children.some(c => this.isPopulated(meta, prop, c, prop.embedded[1]));
|
|
1264
|
-
}
|
|
1265
|
-
return false;
|
|
1266
|
-
}
|
|
1267
|
-
buildFields(meta, populate, joinedProps, qb, alias, options, count = false) {
|
|
1316
|
+
buildFields(meta, populate, joinedProps, qb, alias, options) {
|
|
1268
1317
|
const lazyProps = meta.props.filter(prop => prop.lazy && !populate.some(p => this.isPopulated(meta, prop, p)));
|
|
1269
1318
|
const hasLazyFormulas = meta.props.some(p => p.lazy && p.formula);
|
|
1270
1319
|
const requiresSQLConversion = meta.props.some(p => p.customType?.convertToJSValueSQL && p.persist !== false);
|
|
@@ -1287,7 +1336,7 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
1287
1336
|
where: {},
|
|
1288
1337
|
aliasMap: qb.getAliasMap(),
|
|
1289
1338
|
});
|
|
1290
|
-
this.processField(meta, prop, parts.join('.'), ret
|
|
1339
|
+
this.processField(meta, prop, parts.join('.'), ret);
|
|
1291
1340
|
}
|
|
1292
1341
|
if (!options.fields.includes('*') && !options.fields.includes(`${qb.alias}.*`)) {
|
|
1293
1342
|
ret.unshift(...meta.primaryKeys.filter(pk => !options.fields.includes(pk)));
|
|
@@ -1297,7 +1346,7 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
1297
1346
|
}
|
|
1298
1347
|
}
|
|
1299
1348
|
else if (!Utils.isEmpty(options.exclude) || lazyProps.some(p => !p.formula && (p.kind !== '1:1' || p.owner))) {
|
|
1300
|
-
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));
|
|
1301
1350
|
ret.push(...props.filter(p => !lazyProps.includes(p)).map(p => p.name));
|
|
1302
1351
|
addFormulas = true;
|
|
1303
1352
|
}
|
|
@@ -1309,16 +1358,19 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
1309
1358
|
ret.push('*');
|
|
1310
1359
|
}
|
|
1311
1360
|
if (ret.length > 0 && !hasExplicitFields && addFormulas) {
|
|
1312
|
-
meta.props
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
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
|
+
}
|
|
1322
1374
|
}
|
|
1323
1375
|
// add joined relations after the root entity fields
|
|
1324
1376
|
if (joinedProps.length > 0) {
|
|
@@ -1327,7 +1379,6 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
1327
1379
|
exclude: options.exclude,
|
|
1328
1380
|
populate,
|
|
1329
1381
|
parentTableAlias: alias,
|
|
1330
|
-
count,
|
|
1331
1382
|
...options,
|
|
1332
1383
|
}));
|
|
1333
1384
|
}
|