@mikro-orm/knex 7.0.0-dev.3 → 7.0.0-dev.30
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 +6 -4
- package/AbstractSqlConnection.js +33 -19
- package/AbstractSqlDriver.d.ts +7 -7
- package/AbstractSqlDriver.js +169 -163
- package/AbstractSqlPlatform.js +3 -3
- package/PivotCollectionPersister.d.ts +3 -2
- package/PivotCollectionPersister.js +6 -2
- package/README.md +1 -2
- package/dialects/mssql/MsSqlNativeQueryBuilder.d.ts +2 -0
- package/dialects/mssql/MsSqlNativeQueryBuilder.js +48 -5
- package/dialects/mysql/MySqlPlatform.js +2 -1
- package/dialects/sqlite/BaseSqliteConnection.d.ts +1 -1
- package/dialects/sqlite/BaseSqliteConnection.js +8 -2
- package/dialects/sqlite/BaseSqlitePlatform.d.ts +0 -1
- package/dialects/sqlite/BaseSqlitePlatform.js +0 -4
- package/dialects/sqlite/SqliteSchemaHelper.js +1 -1
- package/package.json +4 -4
- package/query/CriteriaNode.d.ts +1 -1
- package/query/CriteriaNode.js +5 -9
- package/query/CriteriaNodeFactory.js +10 -5
- package/query/NativeQueryBuilder.js +1 -1
- package/query/ObjectCriteriaNode.js +30 -7
- package/query/QueryBuilder.d.ts +15 -1
- package/query/QueryBuilder.js +92 -17
- package/query/QueryBuilderHelper.js +4 -10
- package/query/ScalarCriteriaNode.d.ts +3 -3
- package/query/ScalarCriteriaNode.js +7 -5
- package/schema/DatabaseSchema.js +18 -2
- package/schema/DatabaseTable.d.ts +5 -4
- package/schema/DatabaseTable.js +39 -6
- package/schema/SchemaComparator.js +1 -1
- package/schema/SchemaHelper.d.ts +2 -0
- package/schema/SchemaHelper.js +9 -5
- package/schema/SqlSchemaGenerator.d.ts +6 -1
- package/schema/SqlSchemaGenerator.js +25 -5
- package/typings.d.ts +7 -2
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, getOnConflictFields, getOnConflictReturningFields, helper, isRaw, LoadStrategy, parseJsonSafe, QueryFlag, QueryHelper, QueryOrder, raw, RawQueryFragment, ReferenceKind, Utils, getLoadingStrategy, } 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';
|
|
@@ -63,6 +63,9 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
63
63
|
if (options.lockMode) {
|
|
64
64
|
qb.setLockMode(options.lockMode, options.lockTableAliases);
|
|
65
65
|
}
|
|
66
|
+
if (options.em) {
|
|
67
|
+
await qb.applyJoinedFilters(options.em, options.filters);
|
|
68
|
+
}
|
|
66
69
|
const result = await this.rethrow(qb.execute('all'));
|
|
67
70
|
if (isCursorPagination && !first && !!last) {
|
|
68
71
|
result.reverse();
|
|
@@ -235,7 +238,6 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
235
238
|
let relationPojo = {};
|
|
236
239
|
meta2.props
|
|
237
240
|
.filter(prop => !ref && prop.persist === false && prop.fieldNames)
|
|
238
|
-
.filter(prop => !prop.lazy || populate.some(p => p.field === prop.name || p.all))
|
|
239
241
|
.forEach(prop => {
|
|
240
242
|
/* v8 ignore next 3 */
|
|
241
243
|
if (prop.fieldNames.length > 1) { // composite keys
|
|
@@ -252,6 +254,9 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
252
254
|
: meta2.props.filter(prop => this.platform.shouldHaveColumn(prop, hint.children || []));
|
|
253
255
|
const tz = this.platform.getTimezone();
|
|
254
256
|
for (const prop of targetProps) {
|
|
257
|
+
if (prop.fieldNames.every(name => typeof root[`${relationAlias}__${name}`] === 'undefined')) {
|
|
258
|
+
continue;
|
|
259
|
+
}
|
|
255
260
|
if (prop.fieldNames.length > 1) { // composite keys
|
|
256
261
|
const fk = prop.fieldNames.map(name => root[`${relationAlias}__${name}`]);
|
|
257
262
|
const pk = Utils.mapFlatCompositePrimaryKey(fk, prop);
|
|
@@ -306,15 +311,20 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
306
311
|
});
|
|
307
312
|
}
|
|
308
313
|
async count(entityName, where, options = {}) {
|
|
309
|
-
const meta = this.metadata.
|
|
310
|
-
if (meta
|
|
314
|
+
const meta = this.metadata.get(entityName);
|
|
315
|
+
if (meta.virtual) {
|
|
311
316
|
return this.countVirtual(entityName, where, options);
|
|
312
317
|
}
|
|
313
|
-
|
|
314
|
-
const
|
|
315
|
-
const
|
|
316
|
-
const qb = this.createQueryBuilder(entityName, options.ctx, options.connectionType, false, options.logging)
|
|
317
|
-
|
|
318
|
+
options = { populate: [], ...options };
|
|
319
|
+
const populate = options.populate;
|
|
320
|
+
const joinedProps = this.joinedProps(meta, populate, options);
|
|
321
|
+
const qb = this.createQueryBuilder(entityName, options.ctx, options.connectionType, false, options.logging);
|
|
322
|
+
const populateWhere = this.buildPopulateWhere(meta, joinedProps, options);
|
|
323
|
+
if (meta && !Utils.isEmpty(populate)) {
|
|
324
|
+
this.buildFields(meta, populate, joinedProps, qb, qb.alias, options);
|
|
325
|
+
}
|
|
326
|
+
qb.__populateWhere = options._populateWhere;
|
|
327
|
+
qb.indexHint(options.indexHint)
|
|
318
328
|
.comment(options.comments)
|
|
319
329
|
.hintComment(options.hintComments)
|
|
320
330
|
.groupBy(options.groupBy)
|
|
@@ -322,8 +332,8 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
322
332
|
.populate(populate, joinedProps.length > 0 ? populateWhere : undefined, joinedProps.length > 0 ? options.populateFilter : undefined)
|
|
323
333
|
.withSchema(this.getSchemaName(meta, options))
|
|
324
334
|
.where(where);
|
|
325
|
-
if (
|
|
326
|
-
|
|
335
|
+
if (options.em) {
|
|
336
|
+
await qb.applyJoinedFilters(options.em, options.filters);
|
|
327
337
|
}
|
|
328
338
|
return this.rethrow(qb.getCount());
|
|
329
339
|
}
|
|
@@ -332,7 +342,7 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
332
342
|
const meta = this.metadata.find(entityName);
|
|
333
343
|
const collections = this.extractManyToMany(entityName, data);
|
|
334
344
|
const pks = meta?.primaryKeys ?? [this.config.getNamingStrategy().referenceColumnName()];
|
|
335
|
-
const qb = this.createQueryBuilder(entityName, options.ctx, 'write', options.convertCustomTypes).withSchema(this.getSchemaName(meta, options));
|
|
345
|
+
const qb = this.createQueryBuilder(entityName, options.ctx, 'write', options.convertCustomTypes, options.loggerContext).withSchema(this.getSchemaName(meta, options));
|
|
336
346
|
const res = await this.rethrow(qb.insert(data).execute('run', false));
|
|
337
347
|
res.row = res.row || {};
|
|
338
348
|
let pk;
|
|
@@ -380,7 +390,12 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
380
390
|
sql += ' ' + data.map(() => `select null as ${this.platform.quoteIdentifier(pks[0])}`).join(' union all ');
|
|
381
391
|
}
|
|
382
392
|
const addParams = (prop, row) => {
|
|
383
|
-
|
|
393
|
+
const rowValue = row[prop.name];
|
|
394
|
+
if (prop.nullable && rowValue === null) {
|
|
395
|
+
params.push(null);
|
|
396
|
+
return;
|
|
397
|
+
}
|
|
398
|
+
let value = rowValue ?? prop.default;
|
|
384
399
|
if (prop.kind === ReferenceKind.EMBEDDED && prop.object) {
|
|
385
400
|
if (prop.array && value) {
|
|
386
401
|
value = this.platform.cloneEmbeddable(value);
|
|
@@ -393,14 +408,14 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
393
408
|
value = this.mapDataToFieldNames(value, false, prop.embeddedProps, options.convertCustomTypes);
|
|
394
409
|
}
|
|
395
410
|
}
|
|
396
|
-
if (options.convertCustomTypes && prop.customType) {
|
|
397
|
-
params.push(prop.customType.convertToDatabaseValue(value, this.platform, { key: prop.name, mode: 'query-data' }));
|
|
398
|
-
return;
|
|
399
|
-
}
|
|
400
411
|
if (typeof value === 'undefined' && this.platform.usesDefaultKeyword()) {
|
|
401
412
|
params.push(raw('default'));
|
|
402
413
|
return;
|
|
403
414
|
}
|
|
415
|
+
if (options.convertCustomTypes && prop.customType) {
|
|
416
|
+
params.push(prop.customType.convertToDatabaseValue(value, this.platform, { key: prop.name, mode: 'query-data' }));
|
|
417
|
+
return;
|
|
418
|
+
}
|
|
404
419
|
params.push(value);
|
|
405
420
|
};
|
|
406
421
|
if (fields.length > 0 || this.platform.usesDefaultKeyword()) {
|
|
@@ -434,7 +449,7 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
434
449
|
else {
|
|
435
450
|
const field = prop.fieldNames[0];
|
|
436
451
|
if (!duplicates.includes(field) || !usedDups.includes(field)) {
|
|
437
|
-
if (prop.customType && !prop.object && 'convertToDatabaseValueSQL' in prop.customType && !isRaw(row[prop.name])) {
|
|
452
|
+
if (prop.customType && !prop.object && 'convertToDatabaseValueSQL' in prop.customType && row[prop.name] != null && !isRaw(row[prop.name])) {
|
|
438
453
|
keys.push(prop.customType.convertToDatabaseValueSQL('?', this.platform));
|
|
439
454
|
}
|
|
440
455
|
else {
|
|
@@ -459,7 +474,7 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
459
474
|
if (transform) {
|
|
460
475
|
sql = transform(sql);
|
|
461
476
|
}
|
|
462
|
-
const res = await this.execute(sql, params, 'run', options.ctx);
|
|
477
|
+
const res = await this.execute(sql, params, 'run', options.ctx, options.loggerContext);
|
|
463
478
|
let pk;
|
|
464
479
|
/* v8 ignore next 3 */
|
|
465
480
|
if (pks.length > 1) { // owner has composite pk
|
|
@@ -487,7 +502,7 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
487
502
|
where = { [meta?.primaryKeys[0] ?? pks[0]]: where };
|
|
488
503
|
}
|
|
489
504
|
if (Utils.hasObjectKeys(data)) {
|
|
490
|
-
const qb = this.createQueryBuilder(entityName, options.ctx, 'write', options.convertCustomTypes)
|
|
505
|
+
const qb = this.createQueryBuilder(entityName, options.ctx, 'write', options.convertCustomTypes, options.loggerContext)
|
|
491
506
|
.withSchema(this.getSchemaName(meta, options));
|
|
492
507
|
if (options.upsert) {
|
|
493
508
|
/* v8 ignore next */
|
|
@@ -526,7 +541,7 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
526
541
|
const meta = this.metadata.get(entityName);
|
|
527
542
|
if (options.upsert) {
|
|
528
543
|
const uniqueFields = options.onConflictFields ?? (Utils.isPlainObject(where[0]) ? Object.keys(where[0]).flatMap(key => Utils.splitPrimaryKeys(key)) : meta.primaryKeys);
|
|
529
|
-
const qb = this.createQueryBuilder(entityName, options.ctx, 'write', options.convertCustomTypes).withSchema(this.getSchemaName(meta, options));
|
|
544
|
+
const qb = this.createQueryBuilder(entityName, options.ctx, 'write', options.convertCustomTypes, options.loggerContext).withSchema(this.getSchemaName(meta, options));
|
|
530
545
|
const returning = getOnConflictReturningFields(meta, data[0], uniqueFields, options);
|
|
531
546
|
qb.insert(data)
|
|
532
547
|
.onConflict(uniqueFields)
|
|
@@ -585,7 +600,7 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
585
600
|
if (key in data[idx]) {
|
|
586
601
|
const pks = Utils.getOrderedPrimaryKeys(cond, meta);
|
|
587
602
|
sql += ` when (${pkCond}) then `;
|
|
588
|
-
if (prop.customType && !prop.object && 'convertToDatabaseValueSQL' in prop.customType && !isRaw(data[idx][key])) {
|
|
603
|
+
if (prop.customType && !prop.object && 'convertToDatabaseValueSQL' in prop.customType && data[idx][prop.name] != null && !isRaw(data[idx][key])) {
|
|
589
604
|
sql += prop.customType.convertToDatabaseValueSQL('?', this.platform);
|
|
590
605
|
}
|
|
591
606
|
else {
|
|
@@ -639,7 +654,7 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
639
654
|
/* v8 ignore next */
|
|
640
655
|
sql += returningFields.length > 0 ? ` returning ${returningFields.map(field => this.platform.quoteIdentifier(field)).join(', ')}` : '';
|
|
641
656
|
}
|
|
642
|
-
const res = await this.rethrow(this.execute(sql, params, 'run', options.ctx));
|
|
657
|
+
const res = await this.rethrow(this.execute(sql, params, 'run', options.ctx, options.loggerContext));
|
|
643
658
|
for (let i = 0; i < collections.length; i++) {
|
|
644
659
|
await this.processManyToMany(meta, where[i], collections[i], false, options);
|
|
645
660
|
}
|
|
@@ -651,7 +666,7 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
651
666
|
if (Utils.isPrimaryKey(where) && pks.length === 1) {
|
|
652
667
|
where = { [pks[0]]: where };
|
|
653
668
|
}
|
|
654
|
-
const qb = this.createQueryBuilder(entityName, options.ctx, 'write', false).delete(where).withSchema(this.getSchemaName(meta, options));
|
|
669
|
+
const qb = this.createQueryBuilder(entityName, options.ctx, 'write', false, options.loggerContext).delete(where).withSchema(this.getSchemaName(meta, options));
|
|
655
670
|
return this.rethrow(qb.execute('run', false));
|
|
656
671
|
}
|
|
657
672
|
/**
|
|
@@ -725,14 +740,20 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
725
740
|
const pivotMeta = this.metadata.find(coll.property.pivotEntity);
|
|
726
741
|
let schema = pivotMeta.schema;
|
|
727
742
|
if (schema === '*') {
|
|
728
|
-
|
|
729
|
-
|
|
743
|
+
if (coll.property.owner) {
|
|
744
|
+
schema = wrapped.getSchema() === '*' ? options?.schema ?? this.config.get('schema') : wrapped.getSchema();
|
|
745
|
+
}
|
|
746
|
+
else {
|
|
747
|
+
const targetMeta = coll.property.targetMeta;
|
|
748
|
+
const targetSchema = (coll[0] ?? snap?.[0]) && helper(coll[0] ?? snap?.[0]).getSchema();
|
|
749
|
+
schema = targetMeta.schema === '*' ? options?.schema ?? targetSchema ?? this.config.get('schema') : targetMeta.schema;
|
|
750
|
+
}
|
|
730
751
|
}
|
|
731
752
|
else if (schema == null) {
|
|
732
753
|
schema = this.config.get('schema');
|
|
733
754
|
}
|
|
734
755
|
const tableName = `${schema ?? '_'}.${pivotMeta.tableName}`;
|
|
735
|
-
const persister = groups[tableName] ??= new PivotCollectionPersister(pivotMeta, this, options?.ctx, schema);
|
|
756
|
+
const persister = groups[tableName] ??= new PivotCollectionPersister(pivotMeta, this, options?.ctx, schema, options?.loggerContext);
|
|
736
757
|
persister.enqueueUpdate(coll.property, insertDiff, deleteDiff, pks);
|
|
737
758
|
}
|
|
738
759
|
for (const persister of Utils.values(groups)) {
|
|
@@ -740,110 +761,64 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
740
761
|
}
|
|
741
762
|
}
|
|
742
763
|
async loadFromPivotTable(prop, owners, where = {}, orderBy, ctx, options, pivotJoin) {
|
|
764
|
+
if (owners.length === 0) {
|
|
765
|
+
return {};
|
|
766
|
+
}
|
|
743
767
|
const pivotMeta = this.metadata.find(prop.pivotEntity);
|
|
744
768
|
const pivotProp1 = pivotMeta.relations[prop.owner ? 1 : 0];
|
|
745
769
|
const pivotProp2 = pivotMeta.relations[prop.owner ? 0 : 1];
|
|
746
770
|
const ownerMeta = this.metadata.find(pivotProp2.type);
|
|
747
|
-
options = { ...options };
|
|
748
|
-
const qb = this.createQueryBuilder(prop.pivotEntity, ctx, options.connectionType, undefined, options?.logging)
|
|
749
|
-
.withSchema(this.getSchemaName(pivotMeta, options))
|
|
750
|
-
.indexHint(options.indexHint)
|
|
751
|
-
.comment(options.comments)
|
|
752
|
-
.hintComment(options.hintComments);
|
|
753
|
-
const pivotAlias = qb.alias;
|
|
754
|
-
const pivotKey = pivotProp2.joinColumns.map(column => `${pivotAlias}.${column}`).join(Utils.PK_SEPARATOR);
|
|
755
771
|
const cond = {
|
|
756
|
-
[
|
|
772
|
+
[pivotProp2.name]: { $in: ownerMeta.compositePK ? owners : owners.map(o => o[0]) },
|
|
757
773
|
};
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
}
|
|
765
|
-
|
|
766
|
-
const
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
const
|
|
770
|
-
|
|
771
|
-
...
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
for (const field of targetFields) {
|
|
781
|
-
const f = field.toString();
|
|
782
|
-
fields.unshift(f.includes('.') ? field : `${targetAlias}.${f}`);
|
|
783
|
-
if (RawQueryFragment.isKnownFragment(field)) {
|
|
784
|
-
qb.rawFragments.add(f);
|
|
785
|
-
}
|
|
786
|
-
}
|
|
787
|
-
// we need to handle 1:1 owner auto-joins explicitly, as the QB type is the pivot table, not the target
|
|
788
|
-
populate.forEach(hint => {
|
|
789
|
-
const alias = qb.getNextAlias(prop.targetMeta.tableName);
|
|
790
|
-
qb.leftJoin(`${targetAlias}.${hint.field}`, alias);
|
|
791
|
-
// eslint-disable-next-line dot-notation
|
|
792
|
-
for (const join of Object.values(qb['_joins'])) {
|
|
793
|
-
const [propName] = hint.field.split(':', 2);
|
|
794
|
-
if (join.alias === alias && join.prop.name === propName) {
|
|
795
|
-
fields.push(...qb.helper.mapJoinColumns(qb.type, join));
|
|
796
|
-
}
|
|
797
|
-
}
|
|
798
|
-
});
|
|
799
|
-
}
|
|
800
|
-
qb.select(fields)
|
|
801
|
-
.where({ [pivotProp1.name]: where })
|
|
802
|
-
.orderBy(orderBy)
|
|
803
|
-
.setLockMode(options.lockMode, options.lockTableAliases);
|
|
804
|
-
if (owners.length === 1 && (options.offset != null || options.limit != null)) {
|
|
805
|
-
qb.limit(options.limit, options.offset);
|
|
806
|
-
}
|
|
807
|
-
const res = owners.length ? await this.rethrow(qb.execute('all', { mergeResults: false, mapResults: false })) : [];
|
|
808
|
-
const tmp = {};
|
|
809
|
-
const items = res.map((row) => {
|
|
810
|
-
const root = super.mapResult(row, prop.targetMeta);
|
|
811
|
-
this.mapJoinedProps(root, prop.targetMeta, populate, qb, root, tmp, pivotMeta.className + '.' + pivotProp1.name);
|
|
812
|
-
return root;
|
|
774
|
+
if (!Utils.isEmpty(where)) {
|
|
775
|
+
cond[pivotProp1.name] = { ...where };
|
|
776
|
+
}
|
|
777
|
+
where = cond;
|
|
778
|
+
const populateField = pivotJoin ? `${pivotProp1.name}:ref` : pivotProp1.name;
|
|
779
|
+
const populate = this.autoJoinOneToOneOwner(prop.targetMeta, options?.populate ?? [], options?.fields);
|
|
780
|
+
const childFields = !Utils.isEmpty(options?.fields) ? options.fields.map(f => `${pivotProp1.name}.${f}`) : [];
|
|
781
|
+
const childExclude = !Utils.isEmpty(options?.exclude) ? options.exclude.map(f => `${pivotProp1.name}.${f}`) : [];
|
|
782
|
+
const fields = pivotJoin
|
|
783
|
+
? [pivotProp1.name, pivotProp2.name]
|
|
784
|
+
: [pivotProp1.name, pivotProp2.name, ...childFields];
|
|
785
|
+
const res = await this.find(pivotMeta.className, where, {
|
|
786
|
+
ctx,
|
|
787
|
+
...options,
|
|
788
|
+
fields,
|
|
789
|
+
exclude: childExclude,
|
|
790
|
+
orderBy: this.getPivotOrderBy(prop, pivotProp1, orderBy, options?.orderBy),
|
|
791
|
+
populate: [{ field: populateField, strategy: LoadStrategy.JOINED, joinType: JoinType.innerJoin, children: populate }],
|
|
792
|
+
populateWhere: undefined,
|
|
793
|
+
// @ts-ignore
|
|
794
|
+
_populateWhere: 'infer',
|
|
795
|
+
populateFilter: !Utils.isEmpty(options?.populateFilter) ? { [pivotProp2.name]: options?.populateFilter } : undefined,
|
|
813
796
|
});
|
|
814
|
-
qb.clearRawFragmentsCache();
|
|
815
797
|
const map = {};
|
|
816
|
-
const pkProps = ownerMeta.getPrimaryProps();
|
|
817
798
|
for (const owner of owners) {
|
|
818
|
-
const key = Utils.getPrimaryKeyHash(
|
|
819
|
-
const pkProp = pkProps[idx];
|
|
820
|
-
return pkProp.customType ? pkProp.customType.convertToJSValue(owner[idx], this.platform) : owner[idx];
|
|
821
|
-
}));
|
|
799
|
+
const key = Utils.getPrimaryKeyHash(owner);
|
|
822
800
|
map[key] = [];
|
|
823
801
|
}
|
|
824
|
-
for (const item of
|
|
825
|
-
const key = Utils.getPrimaryKeyHash(
|
|
826
|
-
|
|
827
|
-
return pkProp.customType ? pkProp.customType.convertToJSValue(item[`fk__${col}`], this.platform) : item[`fk__${col}`];
|
|
828
|
-
}));
|
|
829
|
-
map[key].push(item);
|
|
830
|
-
prop.joinColumns.forEach(col => delete item[`fk__${col}`]);
|
|
831
|
-
prop.inverseJoinColumns.forEach((col, idx) => {
|
|
832
|
-
Utils.renameKey(item, `fk__${col}`, prop.targetMeta.primaryKeys[idx]);
|
|
833
|
-
});
|
|
802
|
+
for (const item of res) {
|
|
803
|
+
const key = Utils.getPrimaryKeyHash(Utils.asArray(item[pivotProp2.name]));
|
|
804
|
+
map[key].push(item[pivotProp1.name]);
|
|
834
805
|
}
|
|
835
806
|
return map;
|
|
836
807
|
}
|
|
837
|
-
getPivotOrderBy(prop, pivotProp,
|
|
838
|
-
// FIXME this is ignoring the rest of the array items
|
|
808
|
+
getPivotOrderBy(prop, pivotProp, orderBy, parentOrderBy) {
|
|
839
809
|
if (!Utils.isEmpty(orderBy)) {
|
|
840
|
-
return
|
|
810
|
+
return Utils.asArray(orderBy).map(o => ({ [pivotProp.name]: o }));
|
|
811
|
+
}
|
|
812
|
+
if (prop.kind === ReferenceKind.MANY_TO_MANY && Utils.asArray(parentOrderBy).some(o => o[prop.name])) {
|
|
813
|
+
return Utils.asArray(parentOrderBy)
|
|
814
|
+
.filter(o => o[prop.name])
|
|
815
|
+
.map(o => ({ [pivotProp.name]: o[prop.name] }));
|
|
841
816
|
}
|
|
842
817
|
if (!Utils.isEmpty(prop.orderBy)) {
|
|
843
|
-
return
|
|
818
|
+
return Utils.asArray(prop.orderBy).map(o => ({ [pivotProp.name]: o }));
|
|
844
819
|
}
|
|
845
820
|
if (prop.fixedOrder) {
|
|
846
|
-
return [{ [
|
|
821
|
+
return [{ [prop.fixedOrderColumn]: QueryOrder.ASC }];
|
|
847
822
|
}
|
|
848
823
|
return [];
|
|
849
824
|
}
|
|
@@ -861,7 +836,7 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
861
836
|
const toPopulate = meta.relations
|
|
862
837
|
.filter(prop => prop.kind === ReferenceKind.ONE_TO_ONE && !prop.owner && !prop.lazy && !relationsToPopulate.includes(prop.name))
|
|
863
838
|
.filter(prop => fields.length === 0 || fields.some(f => prop.name === f || prop.name.startsWith(`${String(f)}.`)))
|
|
864
|
-
.map(prop => ({ field: `${prop.name}:ref`, strategy:
|
|
839
|
+
.map(prop => ({ field: `${prop.name}:ref`, strategy: LoadStrategy.JOINED }));
|
|
865
840
|
return [...populate, ...toPopulate];
|
|
866
841
|
}
|
|
867
842
|
/**
|
|
@@ -869,16 +844,17 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
869
844
|
*/
|
|
870
845
|
joinedProps(meta, populate, options) {
|
|
871
846
|
return populate.filter(hint => {
|
|
872
|
-
const [propName
|
|
847
|
+
const [propName] = hint.field.split(':', 2);
|
|
873
848
|
const prop = meta.properties[propName] || {};
|
|
874
|
-
|
|
849
|
+
const strategy = getLoadingStrategy(hint.strategy || prop.strategy || options?.strategy || this.config.get('loadStrategy'), prop.kind);
|
|
850
|
+
if (hint.filter && [ReferenceKind.ONE_TO_ONE, ReferenceKind.MANY_TO_ONE].includes(prop.kind) && !prop.nullable) {
|
|
875
851
|
return true;
|
|
876
852
|
}
|
|
877
853
|
// skip redundant joins for 1:1 owner population hints when using `mapToPk`
|
|
878
854
|
if (prop.kind === ReferenceKind.ONE_TO_ONE && prop.mapToPk && prop.owner) {
|
|
879
855
|
return false;
|
|
880
856
|
}
|
|
881
|
-
if (
|
|
857
|
+
if (strategy !== LoadStrategy.JOINED) {
|
|
882
858
|
// force joined strategy for explicit 1:1 owner populate hint as it would require a join anyway
|
|
883
859
|
return prop.kind === ReferenceKind.ONE_TO_ONE && !prop.owner;
|
|
884
860
|
}
|
|
@@ -889,31 +865,26 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
889
865
|
* @internal
|
|
890
866
|
*/
|
|
891
867
|
mergeJoinedResult(rawResults, meta, joinedProps) {
|
|
868
|
+
if (rawResults.length <= 1) {
|
|
869
|
+
return rawResults;
|
|
870
|
+
}
|
|
892
871
|
const res = [];
|
|
893
872
|
const map = {};
|
|
873
|
+
const collectionsToMerge = {};
|
|
874
|
+
const hints = joinedProps.map(hint => {
|
|
875
|
+
const [propName, ref] = hint.field.split(':', 2);
|
|
876
|
+
return { propName, ref, children: hint.children };
|
|
877
|
+
});
|
|
894
878
|
for (const item of rawResults) {
|
|
895
879
|
const pk = Utils.getCompositeKeyHash(item, meta);
|
|
896
880
|
if (map[pk]) {
|
|
897
|
-
for (const
|
|
898
|
-
const [propName, ref] = hint.field.split(':', 2);
|
|
899
|
-
const prop = meta.properties[propName];
|
|
881
|
+
for (const { propName } of hints) {
|
|
900
882
|
if (!item[propName]) {
|
|
901
883
|
continue;
|
|
902
884
|
}
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
}
|
|
907
|
-
switch (prop.kind) {
|
|
908
|
-
case ReferenceKind.ONE_TO_MANY:
|
|
909
|
-
case ReferenceKind.MANY_TO_MANY:
|
|
910
|
-
map[pk][propName] = this.mergeJoinedResult([...map[pk][propName], ...item[propName]], prop.targetMeta, hint.children ?? []);
|
|
911
|
-
break;
|
|
912
|
-
case ReferenceKind.MANY_TO_ONE:
|
|
913
|
-
case ReferenceKind.ONE_TO_ONE:
|
|
914
|
-
map[pk][propName] = this.mergeJoinedResult([map[pk][propName], item[propName]], prop.targetMeta, hint.children ?? [])[0];
|
|
915
|
-
break;
|
|
916
|
-
}
|
|
885
|
+
collectionsToMerge[pk] ??= {};
|
|
886
|
+
collectionsToMerge[pk][propName] ??= [map[pk][propName]];
|
|
887
|
+
collectionsToMerge[pk][propName].push(item[propName]);
|
|
917
888
|
}
|
|
918
889
|
}
|
|
919
890
|
else {
|
|
@@ -921,17 +892,42 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
921
892
|
res.push(item);
|
|
922
893
|
}
|
|
923
894
|
}
|
|
895
|
+
for (const pk in collectionsToMerge) {
|
|
896
|
+
const entity = map[pk];
|
|
897
|
+
const collections = collectionsToMerge[pk];
|
|
898
|
+
for (const { propName, ref, children } of hints) {
|
|
899
|
+
if (!collections[propName]) {
|
|
900
|
+
continue;
|
|
901
|
+
}
|
|
902
|
+
const prop = meta.properties[propName];
|
|
903
|
+
const items = collections[propName].flat();
|
|
904
|
+
if ([ReferenceKind.ONE_TO_MANY, ReferenceKind.MANY_TO_MANY].includes(prop.kind) && ref) {
|
|
905
|
+
entity[propName] = items;
|
|
906
|
+
continue;
|
|
907
|
+
}
|
|
908
|
+
switch (prop.kind) {
|
|
909
|
+
case ReferenceKind.ONE_TO_MANY:
|
|
910
|
+
case ReferenceKind.MANY_TO_MANY:
|
|
911
|
+
entity[propName] = this.mergeJoinedResult(items, prop.targetMeta, children ?? []);
|
|
912
|
+
break;
|
|
913
|
+
case ReferenceKind.MANY_TO_ONE:
|
|
914
|
+
case ReferenceKind.ONE_TO_ONE:
|
|
915
|
+
entity[propName] = this.mergeJoinedResult(items, prop.targetMeta, children ?? [])[0];
|
|
916
|
+
break;
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
}
|
|
924
920
|
return res;
|
|
925
921
|
}
|
|
926
922
|
getFieldsForJoinedLoad(qb, meta, options) {
|
|
927
923
|
const fields = [];
|
|
928
924
|
const populate = options.populate ?? [];
|
|
929
925
|
const joinedProps = this.joinedProps(meta, populate, options);
|
|
930
|
-
const shouldHaveColumn = (prop, populate, fields) => {
|
|
926
|
+
const shouldHaveColumn = (meta, prop, populate, fields) => {
|
|
931
927
|
if (!this.platform.shouldHaveColumn(prop, populate, options.exclude)) {
|
|
932
928
|
return false;
|
|
933
929
|
}
|
|
934
|
-
if (!fields || fields.includes('*') || prop.primary) {
|
|
930
|
+
if (!fields || fields.includes('*') || prop.primary || meta.root.discriminatorColumn === prop.name) {
|
|
935
931
|
return true;
|
|
936
932
|
}
|
|
937
933
|
return fields.some(f => f === prop.name || f.toString().startsWith(prop.name + '.'));
|
|
@@ -941,17 +937,14 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
941
937
|
if (options.parentJoinPath) {
|
|
942
938
|
// alias all fields in the primary table
|
|
943
939
|
meta.props
|
|
944
|
-
.filter(prop => shouldHaveColumn(prop, populate, options.explicitFields))
|
|
940
|
+
.filter(prop => shouldHaveColumn(meta, prop, populate, options.explicitFields))
|
|
945
941
|
.forEach(prop => fields.push(...this.mapPropToFieldNames(qb, prop, options.parentTableAlias)));
|
|
946
942
|
}
|
|
947
943
|
for (const hint of joinedProps) {
|
|
948
944
|
const [propName, ref] = hint.field.split(':', 2);
|
|
949
945
|
const prop = meta.properties[propName];
|
|
950
946
|
// ignore ref joins of known FKs unless it's a filter hint
|
|
951
|
-
if (ref && !hint.filter && (prop.kind === ReferenceKind.MANY_TO_ONE || (prop.kind === ReferenceKind.ONE_TO_ONE &&
|
|
952
|
-
continue;
|
|
953
|
-
}
|
|
954
|
-
if (options.count && !options?.populateFilter?.[prop.name]) {
|
|
947
|
+
if (ref && !hint.filter && (prop.kind === ReferenceKind.MANY_TO_ONE || (prop.kind === ReferenceKind.ONE_TO_ONE && prop.owner))) {
|
|
955
948
|
continue;
|
|
956
949
|
}
|
|
957
950
|
const meta2 = this.metadata.find(prop.type);
|
|
@@ -962,11 +955,14 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
962
955
|
if (!options.parentJoinPath && populateWhereAll && !hint.filter && !path.startsWith('[populate]')) {
|
|
963
956
|
path = '[populate]' + path;
|
|
964
957
|
}
|
|
958
|
+
const mandatoryToOneProperty = [ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind) && !prop.nullable;
|
|
965
959
|
const joinType = pivotRefJoin
|
|
966
960
|
? JoinType.pivotJoin
|
|
967
|
-
: hint.
|
|
968
|
-
?
|
|
969
|
-
:
|
|
961
|
+
: hint.joinType
|
|
962
|
+
? hint.joinType
|
|
963
|
+
: (hint.filter && !prop.nullable) || mandatoryToOneProperty
|
|
964
|
+
? JoinType.innerJoin
|
|
965
|
+
: JoinType.leftJoin;
|
|
970
966
|
qb.join(field, tableAlias, {}, joinType, path);
|
|
971
967
|
if (pivotRefJoin) {
|
|
972
968
|
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}`)));
|
|
@@ -998,7 +994,7 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
998
994
|
parentJoinPath: path,
|
|
999
995
|
}));
|
|
1000
996
|
}
|
|
1001
|
-
else if (hint.filter || prop.mapToPk) {
|
|
997
|
+
else if (hint.filter || prop.mapToPk || (ref && [ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind))) {
|
|
1002
998
|
fields.push(...prop.referencedColumnNames.map(col => qb.helper.mapper(`${tableAlias}.${col}`, qb.type, undefined, `${tableAlias}__${col}`)));
|
|
1003
999
|
}
|
|
1004
1000
|
}
|
|
@@ -1008,6 +1004,11 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
1008
1004
|
* @internal
|
|
1009
1005
|
*/
|
|
1010
1006
|
mapPropToFieldNames(qb, prop, tableAlias) {
|
|
1007
|
+
if (prop.kind === ReferenceKind.EMBEDDED && !prop.object) {
|
|
1008
|
+
return Object.values(prop.embeddedProps).flatMap(childProp => {
|
|
1009
|
+
return this.mapPropToFieldNames(qb, childProp, tableAlias);
|
|
1010
|
+
});
|
|
1011
|
+
}
|
|
1011
1012
|
const aliased = this.platform.quoteIdentifier(`${tableAlias}__${prop.fieldNames[0]}`);
|
|
1012
1013
|
if (prop.customTypes?.some(type => type?.convertToJSValueSQL)) {
|
|
1013
1014
|
return prop.fieldNames.map((col, idx) => {
|
|
@@ -1073,7 +1074,7 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
1073
1074
|
for (const prop of meta.relations) {
|
|
1074
1075
|
if (collections[prop.name]) {
|
|
1075
1076
|
const pivotMeta = this.metadata.find(prop.pivotEntity);
|
|
1076
|
-
const persister = new PivotCollectionPersister(pivotMeta, this, options?.ctx, options?.schema);
|
|
1077
|
+
const persister = new PivotCollectionPersister(pivotMeta, this, options?.ctx, options?.schema, options?.loggerContext);
|
|
1077
1078
|
persister.enqueueUpdate(prop, collections[prop.name], clear, pks);
|
|
1078
1079
|
await this.rethrow(persister.execute());
|
|
1079
1080
|
}
|
|
@@ -1140,7 +1141,7 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
1140
1141
|
let path = parentPath;
|
|
1141
1142
|
const meta2 = this.metadata.find(prop.type);
|
|
1142
1143
|
const childOrder = orderHint[prop.name];
|
|
1143
|
-
if (![ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind) || !prop.owner || Utils.isPlainObject(childOrder)) {
|
|
1144
|
+
if (prop.kind !== ReferenceKind.SCALAR && (![ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind) || !prop.owner || Utils.isPlainObject(childOrder))) {
|
|
1144
1145
|
path += `.${propName}`;
|
|
1145
1146
|
}
|
|
1146
1147
|
if (prop.kind === ReferenceKind.MANY_TO_MANY && typeof childOrder !== 'object') {
|
|
@@ -1148,10 +1149,10 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
1148
1149
|
}
|
|
1149
1150
|
const join = qb.getJoinForPath(path, { matchPopulateJoins: true });
|
|
1150
1151
|
const propAlias = qb.getAliasForJoinPath(join ?? path, { matchPopulateJoins: true }) ?? parentAlias;
|
|
1151
|
-
if (!join
|
|
1152
|
+
if (!join) {
|
|
1152
1153
|
continue;
|
|
1153
1154
|
}
|
|
1154
|
-
if (![ReferenceKind.SCALAR, ReferenceKind.EMBEDDED].includes(prop.kind) && typeof childOrder === 'object') {
|
|
1155
|
+
if (join && ![ReferenceKind.SCALAR, ReferenceKind.EMBEDDED].includes(prop.kind) && typeof childOrder === 'object') {
|
|
1155
1156
|
const children = this.buildPopulateOrderBy(qb, meta2, Utils.asArray(childOrder), path, explicit, propAlias);
|
|
1156
1157
|
orderBy.push(...children);
|
|
1157
1158
|
continue;
|
|
@@ -1229,7 +1230,7 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
1229
1230
|
}
|
|
1230
1231
|
return ret;
|
|
1231
1232
|
}
|
|
1232
|
-
processField(meta, prop, field, ret
|
|
1233
|
+
processField(meta, prop, field, ret) {
|
|
1233
1234
|
if (!prop || (prop.kind === ReferenceKind.ONE_TO_ONE && !prop.owner)) {
|
|
1234
1235
|
return;
|
|
1235
1236
|
}
|
|
@@ -1242,7 +1243,7 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
1242
1243
|
const top = parts.shift();
|
|
1243
1244
|
for (const key of Object.keys(prop.embeddedProps)) {
|
|
1244
1245
|
if (!top || key === top) {
|
|
1245
|
-
this.processField(meta, prop.embeddedProps[key], parts.join('.'), ret
|
|
1246
|
+
this.processField(meta, prop.embeddedProps[key], parts.join('.'), ret);
|
|
1246
1247
|
}
|
|
1247
1248
|
}
|
|
1248
1249
|
return;
|
|
@@ -1261,7 +1262,7 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
1261
1262
|
}
|
|
1262
1263
|
return false;
|
|
1263
1264
|
}
|
|
1264
|
-
buildFields(meta, populate, joinedProps, qb, alias, options
|
|
1265
|
+
buildFields(meta, populate, joinedProps, qb, alias, options) {
|
|
1265
1266
|
const lazyProps = meta.props.filter(prop => prop.lazy && !populate.some(p => this.isPopulated(meta, prop, p)));
|
|
1266
1267
|
const hasLazyFormulas = meta.props.some(p => p.lazy && p.formula);
|
|
1267
1268
|
const requiresSQLConversion = meta.props.some(p => p.customType?.convertToJSValueSQL && p.persist !== false);
|
|
@@ -1284,11 +1285,14 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
1284
1285
|
where: {},
|
|
1285
1286
|
aliasMap: qb.getAliasMap(),
|
|
1286
1287
|
});
|
|
1287
|
-
this.processField(meta, prop, parts.join('.'), ret
|
|
1288
|
+
this.processField(meta, prop, parts.join('.'), ret);
|
|
1288
1289
|
}
|
|
1289
1290
|
if (!options.fields.includes('*') && !options.fields.includes(`${qb.alias}.*`)) {
|
|
1290
1291
|
ret.unshift(...meta.primaryKeys.filter(pk => !options.fields.includes(pk)));
|
|
1291
1292
|
}
|
|
1293
|
+
if (meta.root.discriminatorColumn && !options.fields.includes(`${qb.alias}.${meta.root.discriminatorColumn}`)) {
|
|
1294
|
+
ret.push(meta.root.discriminatorColumn);
|
|
1295
|
+
}
|
|
1292
1296
|
}
|
|
1293
1297
|
else if (!Utils.isEmpty(options.exclude) || lazyProps.some(p => !p.formula && (p.kind !== '1:1' || p.owner))) {
|
|
1294
1298
|
const props = meta.props.filter(prop => this.platform.shouldHaveColumn(prop, populate, options.exclude, false));
|
|
@@ -1303,16 +1307,19 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
1303
1307
|
ret.push('*');
|
|
1304
1308
|
}
|
|
1305
1309
|
if (ret.length > 0 && !hasExplicitFields && addFormulas) {
|
|
1306
|
-
meta.props
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1310
|
+
for (const prop of meta.props) {
|
|
1311
|
+
if (lazyProps.includes(prop)) {
|
|
1312
|
+
continue;
|
|
1313
|
+
}
|
|
1314
|
+
if (prop.formula) {
|
|
1315
|
+
const a = this.platform.quoteIdentifier(alias);
|
|
1316
|
+
const aliased = this.platform.quoteIdentifier(prop.fieldNames[0]);
|
|
1317
|
+
ret.push(raw(`${prop.formula(a)} as ${aliased}`));
|
|
1318
|
+
}
|
|
1319
|
+
if (!prop.object && (prop.hasConvertToDatabaseValueSQL || prop.hasConvertToJSValueSQL)) {
|
|
1320
|
+
ret.push(prop.name);
|
|
1321
|
+
}
|
|
1322
|
+
}
|
|
1316
1323
|
}
|
|
1317
1324
|
// add joined relations after the root entity fields
|
|
1318
1325
|
if (joinedProps.length > 0) {
|
|
@@ -1321,7 +1328,6 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
1321
1328
|
exclude: options.exclude,
|
|
1322
1329
|
populate,
|
|
1323
1330
|
parentTableAlias: alias,
|
|
1324
|
-
count,
|
|
1325
1331
|
...options,
|
|
1326
1332
|
}));
|
|
1327
1333
|
}
|
package/AbstractSqlPlatform.js
CHANGED
|
@@ -43,13 +43,13 @@ export class AbstractSqlPlatform extends Platform {
|
|
|
43
43
|
return 'rollback';
|
|
44
44
|
}
|
|
45
45
|
getSavepointSQL(savepointName) {
|
|
46
|
-
return `savepoint ${savepointName}`;
|
|
46
|
+
return `savepoint ${this.quoteIdentifier(savepointName)}`;
|
|
47
47
|
}
|
|
48
48
|
getRollbackToSavepointSQL(savepointName) {
|
|
49
|
-
return `rollback to savepoint ${savepointName}`;
|
|
49
|
+
return `rollback to savepoint ${this.quoteIdentifier(savepointName)}`;
|
|
50
50
|
}
|
|
51
51
|
getReleaseSavepointSQL(savepointName) {
|
|
52
|
-
return `release savepoint ${savepointName}`;
|
|
52
|
+
return `release savepoint ${this.quoteIdentifier(savepointName)}`;
|
|
53
53
|
}
|
|
54
54
|
quoteValue(value) {
|
|
55
55
|
if (isRaw(value)) {
|