@mikro-orm/knex 7.0.0-dev.22 → 7.0.0-dev.23
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 +5 -3
- package/AbstractSqlConnection.js +21 -11
- package/AbstractSqlDriver.js +121 -139
- 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 +40 -1
- package/package.json +4 -4
- package/query/CriteriaNode.js +1 -1
- package/query/CriteriaNodeFactory.js +10 -5
- package/query/ObjectCriteriaNode.js +19 -2
- package/query/QueryBuilder.d.ts +5 -0
- package/query/QueryBuilder.js +36 -9
- package/query/ScalarCriteriaNode.d.ts +3 -3
- package/query/ScalarCriteriaNode.js +7 -5
- package/schema/DatabaseTable.d.ts +5 -4
- package/schema/DatabaseTable.js +20 -2
- package/schema/SchemaHelper.js +7 -2
- package/schema/SqlSchemaGenerator.js +0 -3
- package/typings.d.ts +3 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type ControlledTransaction, type Dialect, Kysely } from 'kysely';
|
|
2
|
-
import { type AnyEntity, Connection, type Dictionary, type EntityData, type IsolationLevel, type LoggingOptions, type MaybePromise, type QueryResult, RawQueryFragment, type Transaction, type TransactionEventBroadcaster } from '@mikro-orm/core';
|
|
2
|
+
import { type AnyEntity, Connection, type Dictionary, type EntityData, type IsolationLevel, type LogContext, type LoggingOptions, type MaybePromise, type QueryResult, RawQueryFragment, type Transaction, type TransactionEventBroadcaster } from '@mikro-orm/core';
|
|
3
3
|
import type { AbstractSqlPlatform } from './AbstractSqlPlatform.js';
|
|
4
4
|
import { NativeQueryBuilder } from './query/NativeQueryBuilder.js';
|
|
5
5
|
export declare abstract class AbstractSqlConnection extends Connection {
|
|
@@ -31,15 +31,17 @@ export declare abstract class AbstractSqlConnection extends Connection {
|
|
|
31
31
|
readOnly?: boolean;
|
|
32
32
|
ctx?: ControlledTransaction<any>;
|
|
33
33
|
eventBroadcaster?: TransactionEventBroadcaster;
|
|
34
|
+
loggerContext?: LogContext;
|
|
34
35
|
}): Promise<T>;
|
|
35
36
|
begin(options?: {
|
|
36
37
|
isolationLevel?: IsolationLevel;
|
|
37
38
|
readOnly?: boolean;
|
|
38
39
|
ctx?: ControlledTransaction<any, any>;
|
|
39
40
|
eventBroadcaster?: TransactionEventBroadcaster;
|
|
41
|
+
loggerContext?: LogContext;
|
|
40
42
|
}): Promise<ControlledTransaction<any, any>>;
|
|
41
|
-
commit(ctx: ControlledTransaction<any, any>, eventBroadcaster?: TransactionEventBroadcaster): Promise<void>;
|
|
42
|
-
rollback(ctx: ControlledTransaction<any, any>, eventBroadcaster?: TransactionEventBroadcaster): Promise<void>;
|
|
43
|
+
commit(ctx: ControlledTransaction<any, any>, eventBroadcaster?: TransactionEventBroadcaster, loggerContext?: LogContext): Promise<void>;
|
|
44
|
+
rollback(ctx: ControlledTransaction<any, any>, eventBroadcaster?: TransactionEventBroadcaster, loggerContext?: LogContext): Promise<void>;
|
|
43
45
|
execute<T extends QueryResult | EntityData<AnyEntity> | EntityData<AnyEntity>[] = EntityData<AnyEntity>[]>(query: string | NativeQueryBuilder | RawQueryFragment, params?: readonly unknown[], method?: 'all' | 'get' | 'run', ctx?: Transaction, loggerContext?: LoggingOptions): Promise<T>;
|
|
44
46
|
/**
|
|
45
47
|
* Execute raw SQL queries from file
|
package/AbstractSqlConnection.js
CHANGED
|
@@ -61,11 +61,11 @@ export class AbstractSqlConnection extends Connection {
|
|
|
61
61
|
const trx = await this.begin(options);
|
|
62
62
|
try {
|
|
63
63
|
const ret = await cb(trx);
|
|
64
|
-
await this.commit(trx, options.eventBroadcaster);
|
|
64
|
+
await this.commit(trx, options.eventBroadcaster, options.loggerContext);
|
|
65
65
|
return ret;
|
|
66
66
|
}
|
|
67
67
|
catch (error) {
|
|
68
|
-
await this.rollback(trx, options.eventBroadcaster);
|
|
68
|
+
await this.rollback(trx, options.eventBroadcaster, options.loggerContext);
|
|
69
69
|
throw error;
|
|
70
70
|
}
|
|
71
71
|
}
|
|
@@ -78,7 +78,7 @@ export class AbstractSqlConnection extends Connection {
|
|
|
78
78
|
const trx = await options.ctx.savepoint(savepointName).execute();
|
|
79
79
|
Reflect.defineProperty(trx, 'index', { value: ctx.index + 1 });
|
|
80
80
|
Reflect.defineProperty(trx, 'savepointName', { value: savepointName });
|
|
81
|
-
this.logQuery(this.platform.getSavepointSQL(savepointName));
|
|
81
|
+
this.logQuery(this.platform.getSavepointSQL(savepointName), options.loggerContext);
|
|
82
82
|
await options.eventBroadcaster?.dispatchEvent(EventType.afterTransactionStart, trx);
|
|
83
83
|
return trx;
|
|
84
84
|
}
|
|
@@ -92,36 +92,46 @@ export class AbstractSqlConnection extends Connection {
|
|
|
92
92
|
trxBuilder = trxBuilder.setAccessMode('read only');
|
|
93
93
|
}
|
|
94
94
|
const trx = await trxBuilder.execute();
|
|
95
|
-
|
|
96
|
-
|
|
95
|
+
if (options.ctx) {
|
|
96
|
+
const ctx = options.ctx;
|
|
97
|
+
ctx.index ??= 0;
|
|
98
|
+
const savepointName = `trx${ctx.index + 1}`;
|
|
99
|
+
Reflect.defineProperty(trx, 'index', { value: ctx.index + 1 });
|
|
100
|
+
Reflect.defineProperty(trx, 'savepointName', { value: savepointName });
|
|
101
|
+
this.logQuery(this.platform.getSavepointSQL(savepointName), options.loggerContext);
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
for (const query of this.platform.getBeginTransactionSQL(options)) {
|
|
105
|
+
this.logQuery(query, options.loggerContext);
|
|
106
|
+
}
|
|
97
107
|
}
|
|
98
108
|
await options.eventBroadcaster?.dispatchEvent(EventType.afterTransactionStart, trx);
|
|
99
109
|
return trx;
|
|
100
110
|
}
|
|
101
|
-
async commit(ctx, eventBroadcaster) {
|
|
111
|
+
async commit(ctx, eventBroadcaster, loggerContext) {
|
|
102
112
|
if (ctx.isRolledBack) {
|
|
103
113
|
return;
|
|
104
114
|
}
|
|
105
115
|
await eventBroadcaster?.dispatchEvent(EventType.beforeTransactionCommit, ctx);
|
|
106
116
|
if ('savepointName' in ctx) {
|
|
107
117
|
await ctx.releaseSavepoint(ctx.savepointName).execute();
|
|
108
|
-
this.logQuery(this.platform.getReleaseSavepointSQL(ctx.savepointName));
|
|
118
|
+
this.logQuery(this.platform.getReleaseSavepointSQL(ctx.savepointName), loggerContext);
|
|
109
119
|
}
|
|
110
120
|
else {
|
|
111
121
|
await ctx.commit().execute();
|
|
112
|
-
this.logQuery(this.platform.getCommitTransactionSQL());
|
|
122
|
+
this.logQuery(this.platform.getCommitTransactionSQL(), loggerContext);
|
|
113
123
|
}
|
|
114
124
|
await eventBroadcaster?.dispatchEvent(EventType.afterTransactionCommit, ctx);
|
|
115
125
|
}
|
|
116
|
-
async rollback(ctx, eventBroadcaster) {
|
|
126
|
+
async rollback(ctx, eventBroadcaster, loggerContext) {
|
|
117
127
|
await eventBroadcaster?.dispatchEvent(EventType.beforeTransactionRollback, ctx);
|
|
118
128
|
if ('savepointName' in ctx) {
|
|
119
129
|
await ctx.rollbackToSavepoint(ctx.savepointName).execute();
|
|
120
|
-
this.logQuery(this.platform.getRollbackToSavepointSQL(ctx.savepointName));
|
|
130
|
+
this.logQuery(this.platform.getRollbackToSavepointSQL(ctx.savepointName), loggerContext);
|
|
121
131
|
}
|
|
122
132
|
else {
|
|
123
133
|
await ctx.rollback().execute();
|
|
124
|
-
this.logQuery(this.platform.getRollbackTransactionSQL());
|
|
134
|
+
this.logQuery(this.platform.getRollbackTransactionSQL(), loggerContext);
|
|
125
135
|
}
|
|
126
136
|
await eventBroadcaster?.dispatchEvent(EventType.afterTransactionRollback, ctx);
|
|
127
137
|
}
|
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';
|
|
@@ -331,6 +331,9 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
331
331
|
if (meta && !Utils.isEmpty(populate)) {
|
|
332
332
|
this.buildFields(meta, populate, joinedProps, qb, qb.alias, options, true);
|
|
333
333
|
}
|
|
334
|
+
if (options.em) {
|
|
335
|
+
await qb.applyJoinedFilters(options.em, options.filters);
|
|
336
|
+
}
|
|
334
337
|
return this.rethrow(qb.getCount());
|
|
335
338
|
}
|
|
336
339
|
async nativeInsert(entityName, data, options = {}) {
|
|
@@ -338,7 +341,7 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
338
341
|
const meta = this.metadata.find(entityName);
|
|
339
342
|
const collections = this.extractManyToMany(entityName, data);
|
|
340
343
|
const pks = meta?.primaryKeys ?? [this.config.getNamingStrategy().referenceColumnName()];
|
|
341
|
-
const qb = this.createQueryBuilder(entityName, options.ctx, 'write', options.convertCustomTypes).withSchema(this.getSchemaName(meta, options));
|
|
344
|
+
const qb = this.createQueryBuilder(entityName, options.ctx, 'write', options.convertCustomTypes, options.loggerContext).withSchema(this.getSchemaName(meta, options));
|
|
342
345
|
const res = await this.rethrow(qb.insert(data).execute('run', false));
|
|
343
346
|
res.row = res.row || {};
|
|
344
347
|
let pk;
|
|
@@ -399,14 +402,14 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
399
402
|
value = this.mapDataToFieldNames(value, false, prop.embeddedProps, options.convertCustomTypes);
|
|
400
403
|
}
|
|
401
404
|
}
|
|
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
405
|
if (typeof value === 'undefined' && this.platform.usesDefaultKeyword()) {
|
|
407
406
|
params.push(raw('default'));
|
|
408
407
|
return;
|
|
409
408
|
}
|
|
409
|
+
if (options.convertCustomTypes && prop.customType) {
|
|
410
|
+
params.push(prop.customType.convertToDatabaseValue(value, this.platform, { key: prop.name, mode: 'query-data' }));
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
410
413
|
params.push(value);
|
|
411
414
|
};
|
|
412
415
|
if (fields.length > 0 || this.platform.usesDefaultKeyword()) {
|
|
@@ -465,7 +468,7 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
465
468
|
if (transform) {
|
|
466
469
|
sql = transform(sql);
|
|
467
470
|
}
|
|
468
|
-
const res = await this.execute(sql, params, 'run', options.ctx);
|
|
471
|
+
const res = await this.execute(sql, params, 'run', options.ctx, options.loggerContext);
|
|
469
472
|
let pk;
|
|
470
473
|
/* v8 ignore next 3 */
|
|
471
474
|
if (pks.length > 1) { // owner has composite pk
|
|
@@ -493,7 +496,7 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
493
496
|
where = { [meta?.primaryKeys[0] ?? pks[0]]: where };
|
|
494
497
|
}
|
|
495
498
|
if (Utils.hasObjectKeys(data)) {
|
|
496
|
-
const qb = this.createQueryBuilder(entityName, options.ctx, 'write', options.convertCustomTypes)
|
|
499
|
+
const qb = this.createQueryBuilder(entityName, options.ctx, 'write', options.convertCustomTypes, options.loggerContext)
|
|
497
500
|
.withSchema(this.getSchemaName(meta, options));
|
|
498
501
|
if (options.upsert) {
|
|
499
502
|
/* v8 ignore next */
|
|
@@ -532,7 +535,7 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
532
535
|
const meta = this.metadata.get(entityName);
|
|
533
536
|
if (options.upsert) {
|
|
534
537
|
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));
|
|
538
|
+
const qb = this.createQueryBuilder(entityName, options.ctx, 'write', options.convertCustomTypes, options.loggerContext).withSchema(this.getSchemaName(meta, options));
|
|
536
539
|
const returning = getOnConflictReturningFields(meta, data[0], uniqueFields, options);
|
|
537
540
|
qb.insert(data)
|
|
538
541
|
.onConflict(uniqueFields)
|
|
@@ -645,7 +648,7 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
645
648
|
/* v8 ignore next */
|
|
646
649
|
sql += returningFields.length > 0 ? ` returning ${returningFields.map(field => this.platform.quoteIdentifier(field)).join(', ')}` : '';
|
|
647
650
|
}
|
|
648
|
-
const res = await this.rethrow(this.execute(sql, params, 'run', options.ctx));
|
|
651
|
+
const res = await this.rethrow(this.execute(sql, params, 'run', options.ctx, options.loggerContext));
|
|
649
652
|
for (let i = 0; i < collections.length; i++) {
|
|
650
653
|
await this.processManyToMany(meta, where[i], collections[i], false, options);
|
|
651
654
|
}
|
|
@@ -657,7 +660,7 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
657
660
|
if (Utils.isPrimaryKey(where) && pks.length === 1) {
|
|
658
661
|
where = { [pks[0]]: where };
|
|
659
662
|
}
|
|
660
|
-
const qb = this.createQueryBuilder(entityName, options.ctx, 'write', false).delete(where).withSchema(this.getSchemaName(meta, options));
|
|
663
|
+
const qb = this.createQueryBuilder(entityName, options.ctx, 'write', false, options.loggerContext).delete(where).withSchema(this.getSchemaName(meta, options));
|
|
661
664
|
return this.rethrow(qb.execute('run', false));
|
|
662
665
|
}
|
|
663
666
|
/**
|
|
@@ -744,7 +747,7 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
744
747
|
schema = this.config.get('schema');
|
|
745
748
|
}
|
|
746
749
|
const tableName = `${schema ?? '_'}.${pivotMeta.tableName}`;
|
|
747
|
-
const persister = groups[tableName] ??= new PivotCollectionPersister(pivotMeta, this, options?.ctx, schema);
|
|
750
|
+
const persister = groups[tableName] ??= new PivotCollectionPersister(pivotMeta, this, options?.ctx, schema, options?.loggerContext);
|
|
748
751
|
persister.enqueueUpdate(coll.property, insertDiff, deleteDiff, pks);
|
|
749
752
|
}
|
|
750
753
|
for (const persister of Utils.values(groups)) {
|
|
@@ -752,112 +755,64 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
752
755
|
}
|
|
753
756
|
}
|
|
754
757
|
async loadFromPivotTable(prop, owners, where = {}, orderBy, ctx, options, pivotJoin) {
|
|
758
|
+
if (owners.length === 0) {
|
|
759
|
+
return {};
|
|
760
|
+
}
|
|
755
761
|
const pivotMeta = this.metadata.find(prop.pivotEntity);
|
|
756
762
|
const pivotProp1 = pivotMeta.relations[prop.owner ? 1 : 0];
|
|
757
763
|
const pivotProp2 = pivotMeta.relations[prop.owner ? 0 : 1];
|
|
758
764
|
const ownerMeta = this.metadata.find(pivotProp2.type);
|
|
759
|
-
options = { ...options };
|
|
760
|
-
const qb = this.createQueryBuilder(prop.pivotEntity, ctx, options.connectionType, undefined, options?.logging)
|
|
761
|
-
.withSchema(this.getSchemaName(pivotMeta, options))
|
|
762
|
-
.indexHint(options.indexHint)
|
|
763
|
-
.comment(options.comments)
|
|
764
|
-
.hintComment(options.hintComments);
|
|
765
|
-
const pivotAlias = qb.alias;
|
|
766
|
-
const pivotKey = pivotProp2.joinColumns.map(column => `${pivotAlias}.${column}`).join(Utils.PK_SEPARATOR);
|
|
767
765
|
const cond = {
|
|
768
|
-
[
|
|
766
|
+
[pivotProp2.name]: { $in: ownerMeta.compositePK ? owners : owners.map(o => o[0]) },
|
|
769
767
|
};
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
}
|
|
777
|
-
|
|
778
|
-
const
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
const
|
|
782
|
-
|
|
783
|
-
...
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
const additionalFields = [];
|
|
793
|
-
for (const field of targetFields) {
|
|
794
|
-
const f = field.toString();
|
|
795
|
-
additionalFields.push(f.includes('.') ? field : `${targetAlias}.${f}`);
|
|
796
|
-
if (RawQueryFragment.isKnownFragment(field)) {
|
|
797
|
-
qb.rawFragments.add(f);
|
|
798
|
-
}
|
|
799
|
-
}
|
|
800
|
-
fields.unshift(...additionalFields);
|
|
801
|
-
// we need to handle 1:1 owner auto-joins explicitly, as the QB type is the pivot table, not the target
|
|
802
|
-
populate.forEach(hint => {
|
|
803
|
-
const alias = qb.getNextAlias(prop.targetMeta.tableName);
|
|
804
|
-
qb.leftJoin(`${targetAlias}.${hint.field}`, alias);
|
|
805
|
-
// eslint-disable-next-line dot-notation
|
|
806
|
-
for (const join of Object.values(qb['_joins'])) {
|
|
807
|
-
const [propName] = hint.field.split(':', 2);
|
|
808
|
-
if (join.alias === alias && join.prop.name === propName) {
|
|
809
|
-
fields.push(...qb.helper.mapJoinColumns(qb.type, join));
|
|
810
|
-
}
|
|
811
|
-
}
|
|
812
|
-
});
|
|
813
|
-
}
|
|
814
|
-
qb.select(fields)
|
|
815
|
-
.where({ [pivotProp1.name]: where })
|
|
816
|
-
.orderBy(orderBy)
|
|
817
|
-
.setLockMode(options.lockMode, options.lockTableAliases);
|
|
818
|
-
if (owners.length === 1 && (options.offset != null || options.limit != null)) {
|
|
819
|
-
qb.limit(options.limit, options.offset);
|
|
820
|
-
}
|
|
821
|
-
const res = owners.length ? await this.rethrow(qb.execute('all', { mergeResults: false, mapResults: false })) : [];
|
|
822
|
-
const tmp = {};
|
|
823
|
-
const items = res.map((row) => {
|
|
824
|
-
const root = super.mapResult(row, prop.targetMeta);
|
|
825
|
-
this.mapJoinedProps(root, prop.targetMeta, populate, qb, root, tmp, pivotMeta.className + '.' + pivotProp1.name);
|
|
826
|
-
return root;
|
|
768
|
+
if (!Utils.isEmpty(where)) {
|
|
769
|
+
cond[pivotProp1.name] = { ...where };
|
|
770
|
+
}
|
|
771
|
+
where = cond;
|
|
772
|
+
const populateField = pivotJoin ? `${pivotProp1.name}:ref` : pivotProp1.name;
|
|
773
|
+
const populate = this.autoJoinOneToOneOwner(prop.targetMeta, options?.populate ?? [], options?.fields);
|
|
774
|
+
const childFields = !Utils.isEmpty(options?.fields) ? options.fields.map(f => `${pivotProp1.name}.${f}`) : [];
|
|
775
|
+
const childExclude = !Utils.isEmpty(options?.exclude) ? options.exclude.map(f => `${pivotProp1.name}.${f}`) : [];
|
|
776
|
+
const fields = pivotJoin
|
|
777
|
+
? [pivotProp1.name, pivotProp2.name]
|
|
778
|
+
: [pivotProp1.name, pivotProp2.name, ...childFields];
|
|
779
|
+
const res = await this.find(pivotMeta.className, where, {
|
|
780
|
+
ctx,
|
|
781
|
+
...options,
|
|
782
|
+
fields,
|
|
783
|
+
exclude: childExclude,
|
|
784
|
+
orderBy: this.getPivotOrderBy(prop, pivotProp1, orderBy, options?.orderBy),
|
|
785
|
+
populate: [{ field: populateField, strategy: LoadStrategy.JOINED, joinType: JoinType.innerJoin, children: populate }],
|
|
786
|
+
populateWhere: undefined,
|
|
787
|
+
// @ts-ignore
|
|
788
|
+
_populateWhere: 'infer',
|
|
789
|
+
populateFilter: !Utils.isEmpty(options?.populateFilter) ? { [pivotProp2.name]: options?.populateFilter } : undefined,
|
|
827
790
|
});
|
|
828
|
-
qb.clearRawFragmentsCache();
|
|
829
791
|
const map = {};
|
|
830
|
-
const pkProps = ownerMeta.getPrimaryProps();
|
|
831
792
|
for (const owner of owners) {
|
|
832
|
-
const key = Utils.getPrimaryKeyHash(
|
|
833
|
-
const pkProp = pkProps[idx];
|
|
834
|
-
return pkProp.customType ? pkProp.customType.convertToJSValue(owner[idx], this.platform) : owner[idx];
|
|
835
|
-
}));
|
|
793
|
+
const key = Utils.getPrimaryKeyHash(owner);
|
|
836
794
|
map[key] = [];
|
|
837
795
|
}
|
|
838
|
-
for (const item of
|
|
839
|
-
const key = Utils.getPrimaryKeyHash(
|
|
840
|
-
|
|
841
|
-
return pkProp.customType ? pkProp.customType.convertToJSValue(item[`fk__${col}`], this.platform) : item[`fk__${col}`];
|
|
842
|
-
}));
|
|
843
|
-
map[key].push(item);
|
|
844
|
-
prop.joinColumns.forEach(col => delete item[`fk__${col}`]);
|
|
845
|
-
prop.inverseJoinColumns.forEach((col, idx) => {
|
|
846
|
-
Utils.renameKey(item, `fk__${col}`, prop.targetMeta.primaryKeys[idx]);
|
|
847
|
-
});
|
|
796
|
+
for (const item of res) {
|
|
797
|
+
const key = Utils.getPrimaryKeyHash(Utils.asArray(item[pivotProp2.name]));
|
|
798
|
+
map[key].push(item[pivotProp1.name]);
|
|
848
799
|
}
|
|
849
800
|
return map;
|
|
850
801
|
}
|
|
851
|
-
getPivotOrderBy(prop, pivotProp,
|
|
852
|
-
// FIXME this is ignoring the rest of the array items
|
|
802
|
+
getPivotOrderBy(prop, pivotProp, orderBy, parentOrderBy) {
|
|
853
803
|
if (!Utils.isEmpty(orderBy)) {
|
|
854
|
-
return
|
|
804
|
+
return Utils.asArray(orderBy).map(o => ({ [pivotProp.name]: o }));
|
|
805
|
+
}
|
|
806
|
+
if (prop.kind === ReferenceKind.MANY_TO_MANY && Utils.asArray(parentOrderBy).some(o => o[prop.name])) {
|
|
807
|
+
return Utils.asArray(parentOrderBy)
|
|
808
|
+
.filter(o => o[prop.name])
|
|
809
|
+
.map(o => ({ [pivotProp.name]: o[prop.name] }));
|
|
855
810
|
}
|
|
856
811
|
if (!Utils.isEmpty(prop.orderBy)) {
|
|
857
|
-
return
|
|
812
|
+
return Utils.asArray(prop.orderBy).map(o => ({ [pivotProp.name]: o }));
|
|
858
813
|
}
|
|
859
814
|
if (prop.fixedOrder) {
|
|
860
|
-
return [{ [
|
|
815
|
+
return [{ [prop.fixedOrderColumn]: QueryOrder.ASC }];
|
|
861
816
|
}
|
|
862
817
|
return [];
|
|
863
818
|
}
|
|
@@ -875,7 +830,7 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
875
830
|
const toPopulate = meta.relations
|
|
876
831
|
.filter(prop => prop.kind === ReferenceKind.ONE_TO_ONE && !prop.owner && !prop.lazy && !relationsToPopulate.includes(prop.name))
|
|
877
832
|
.filter(prop => fields.length === 0 || fields.some(f => prop.name === f || prop.name.startsWith(`${String(f)}.`)))
|
|
878
|
-
.map(prop => ({ field: `${prop.name}:ref`, strategy:
|
|
833
|
+
.map(prop => ({ field: `${prop.name}:ref`, strategy: LoadStrategy.JOINED }));
|
|
879
834
|
return [...populate, ...toPopulate];
|
|
880
835
|
}
|
|
881
836
|
/**
|
|
@@ -883,16 +838,17 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
883
838
|
*/
|
|
884
839
|
joinedProps(meta, populate, options) {
|
|
885
840
|
return populate.filter(hint => {
|
|
886
|
-
const [propName
|
|
841
|
+
const [propName] = hint.field.split(':', 2);
|
|
887
842
|
const prop = meta.properties[propName] || {};
|
|
888
|
-
|
|
843
|
+
const strategy = getLoadingStrategy(hint.strategy || prop.strategy || options?.strategy || this.config.get('loadStrategy'), prop.kind);
|
|
844
|
+
if (hint.filter && [ReferenceKind.ONE_TO_ONE, ReferenceKind.MANY_TO_ONE].includes(prop.kind) && !prop.nullable) {
|
|
889
845
|
return true;
|
|
890
846
|
}
|
|
891
847
|
// skip redundant joins for 1:1 owner population hints when using `mapToPk`
|
|
892
848
|
if (prop.kind === ReferenceKind.ONE_TO_ONE && prop.mapToPk && prop.owner) {
|
|
893
849
|
return false;
|
|
894
850
|
}
|
|
895
|
-
if (
|
|
851
|
+
if (strategy !== LoadStrategy.JOINED) {
|
|
896
852
|
// force joined strategy for explicit 1:1 owner populate hint as it would require a join anyway
|
|
897
853
|
return prop.kind === ReferenceKind.ONE_TO_ONE && !prop.owner;
|
|
898
854
|
}
|
|
@@ -903,31 +859,26 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
903
859
|
* @internal
|
|
904
860
|
*/
|
|
905
861
|
mergeJoinedResult(rawResults, meta, joinedProps) {
|
|
862
|
+
if (rawResults.length <= 1) {
|
|
863
|
+
return rawResults;
|
|
864
|
+
}
|
|
906
865
|
const res = [];
|
|
907
866
|
const map = {};
|
|
867
|
+
const collectionsToMerge = {};
|
|
868
|
+
const hints = joinedProps.map(hint => {
|
|
869
|
+
const [propName, ref] = hint.field.split(':', 2);
|
|
870
|
+
return { propName, ref, children: hint.children };
|
|
871
|
+
});
|
|
908
872
|
for (const item of rawResults) {
|
|
909
873
|
const pk = Utils.getCompositeKeyHash(item, meta);
|
|
910
874
|
if (map[pk]) {
|
|
911
|
-
for (const
|
|
912
|
-
const [propName, ref] = hint.field.split(':', 2);
|
|
913
|
-
const prop = meta.properties[propName];
|
|
875
|
+
for (const { propName } of hints) {
|
|
914
876
|
if (!item[propName]) {
|
|
915
877
|
continue;
|
|
916
878
|
}
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
}
|
|
921
|
-
switch (prop.kind) {
|
|
922
|
-
case ReferenceKind.ONE_TO_MANY:
|
|
923
|
-
case ReferenceKind.MANY_TO_MANY:
|
|
924
|
-
map[pk][propName] = this.mergeJoinedResult([...map[pk][propName], ...item[propName]], prop.targetMeta, hint.children ?? []);
|
|
925
|
-
break;
|
|
926
|
-
case ReferenceKind.MANY_TO_ONE:
|
|
927
|
-
case ReferenceKind.ONE_TO_ONE:
|
|
928
|
-
map[pk][propName] = this.mergeJoinedResult([map[pk][propName], item[propName]], prop.targetMeta, hint.children ?? [])[0];
|
|
929
|
-
break;
|
|
930
|
-
}
|
|
879
|
+
collectionsToMerge[pk] ??= {};
|
|
880
|
+
collectionsToMerge[pk][propName] ??= [map[pk][propName]];
|
|
881
|
+
collectionsToMerge[pk][propName].push(item[propName]);
|
|
931
882
|
}
|
|
932
883
|
}
|
|
933
884
|
else {
|
|
@@ -935,6 +886,31 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
935
886
|
res.push(item);
|
|
936
887
|
}
|
|
937
888
|
}
|
|
889
|
+
for (const pk in collectionsToMerge) {
|
|
890
|
+
const entity = map[pk];
|
|
891
|
+
const collections = collectionsToMerge[pk];
|
|
892
|
+
for (const { propName, ref, children } of hints) {
|
|
893
|
+
if (!collections[propName]) {
|
|
894
|
+
continue;
|
|
895
|
+
}
|
|
896
|
+
const prop = meta.properties[propName];
|
|
897
|
+
const items = collections[propName].flat();
|
|
898
|
+
if ([ReferenceKind.ONE_TO_MANY, ReferenceKind.MANY_TO_MANY].includes(prop.kind) && ref) {
|
|
899
|
+
entity[propName] = items;
|
|
900
|
+
continue;
|
|
901
|
+
}
|
|
902
|
+
switch (prop.kind) {
|
|
903
|
+
case ReferenceKind.ONE_TO_MANY:
|
|
904
|
+
case ReferenceKind.MANY_TO_MANY:
|
|
905
|
+
entity[propName] = this.mergeJoinedResult(items, prop.targetMeta, children ?? []);
|
|
906
|
+
break;
|
|
907
|
+
case ReferenceKind.MANY_TO_ONE:
|
|
908
|
+
case ReferenceKind.ONE_TO_ONE:
|
|
909
|
+
entity[propName] = this.mergeJoinedResult(items, prop.targetMeta, children ?? [])[0];
|
|
910
|
+
break;
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
}
|
|
938
914
|
return res;
|
|
939
915
|
}
|
|
940
916
|
getFieldsForJoinedLoad(qb, meta, options) {
|
|
@@ -962,7 +938,7 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
962
938
|
const [propName, ref] = hint.field.split(':', 2);
|
|
963
939
|
const prop = meta.properties[propName];
|
|
964
940
|
// ignore ref joins of known FKs unless it's a filter hint
|
|
965
|
-
if (ref && !hint.filter && (prop.kind === ReferenceKind.MANY_TO_ONE || (prop.kind === ReferenceKind.ONE_TO_ONE &&
|
|
941
|
+
if (ref && !hint.filter && (prop.kind === ReferenceKind.MANY_TO_ONE || (prop.kind === ReferenceKind.ONE_TO_ONE && prop.owner))) {
|
|
966
942
|
continue;
|
|
967
943
|
}
|
|
968
944
|
if (options.count && !options?.populateFilter?.[prop.name]) {
|
|
@@ -976,11 +952,14 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
976
952
|
if (!options.parentJoinPath && populateWhereAll && !hint.filter && !path.startsWith('[populate]')) {
|
|
977
953
|
path = '[populate]' + path;
|
|
978
954
|
}
|
|
955
|
+
const mandatoryToOneProperty = [ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind) && !prop.nullable;
|
|
979
956
|
const joinType = pivotRefJoin
|
|
980
957
|
? JoinType.pivotJoin
|
|
981
|
-
: hint.
|
|
982
|
-
?
|
|
983
|
-
:
|
|
958
|
+
: hint.joinType
|
|
959
|
+
? hint.joinType
|
|
960
|
+
: (hint.filter && !prop.nullable) || mandatoryToOneProperty
|
|
961
|
+
? JoinType.innerJoin
|
|
962
|
+
: JoinType.leftJoin;
|
|
984
963
|
qb.join(field, tableAlias, {}, joinType, path);
|
|
985
964
|
if (pivotRefJoin) {
|
|
986
965
|
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}`)));
|
|
@@ -1012,7 +991,7 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
1012
991
|
parentJoinPath: path,
|
|
1013
992
|
}));
|
|
1014
993
|
}
|
|
1015
|
-
else if (hint.filter || prop.mapToPk) {
|
|
994
|
+
else if (hint.filter || prop.mapToPk || (ref && [ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind))) {
|
|
1016
995
|
fields.push(...prop.referencedColumnNames.map(col => qb.helper.mapper(`${tableAlias}.${col}`, qb.type, undefined, `${tableAlias}__${col}`)));
|
|
1017
996
|
}
|
|
1018
997
|
}
|
|
@@ -1087,7 +1066,7 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
1087
1066
|
for (const prop of meta.relations) {
|
|
1088
1067
|
if (collections[prop.name]) {
|
|
1089
1068
|
const pivotMeta = this.metadata.find(prop.pivotEntity);
|
|
1090
|
-
const persister = new PivotCollectionPersister(pivotMeta, this, options?.ctx, options?.schema);
|
|
1069
|
+
const persister = new PivotCollectionPersister(pivotMeta, this, options?.ctx, options?.schema, options?.loggerContext);
|
|
1091
1070
|
persister.enqueueUpdate(prop, collections[prop.name], clear, pks);
|
|
1092
1071
|
await this.rethrow(persister.execute());
|
|
1093
1072
|
}
|
|
@@ -1154,7 +1133,7 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
1154
1133
|
let path = parentPath;
|
|
1155
1134
|
const meta2 = this.metadata.find(prop.type);
|
|
1156
1135
|
const childOrder = orderHint[prop.name];
|
|
1157
|
-
if (![ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind) || !prop.owner || Utils.isPlainObject(childOrder)) {
|
|
1136
|
+
if (prop.kind !== ReferenceKind.SCALAR && (![ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind) || !prop.owner || Utils.isPlainObject(childOrder))) {
|
|
1158
1137
|
path += `.${propName}`;
|
|
1159
1138
|
}
|
|
1160
1139
|
if (prop.kind === ReferenceKind.MANY_TO_MANY && typeof childOrder !== 'object') {
|
|
@@ -1162,10 +1141,10 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
1162
1141
|
}
|
|
1163
1142
|
const join = qb.getJoinForPath(path, { matchPopulateJoins: true });
|
|
1164
1143
|
const propAlias = qb.getAliasForJoinPath(join ?? path, { matchPopulateJoins: true }) ?? parentAlias;
|
|
1165
|
-
if (!join
|
|
1144
|
+
if (!join) {
|
|
1166
1145
|
continue;
|
|
1167
1146
|
}
|
|
1168
|
-
if (![ReferenceKind.SCALAR, ReferenceKind.EMBEDDED].includes(prop.kind) && typeof childOrder === 'object') {
|
|
1147
|
+
if (join && ![ReferenceKind.SCALAR, ReferenceKind.EMBEDDED].includes(prop.kind) && typeof childOrder === 'object') {
|
|
1169
1148
|
const children = this.buildPopulateOrderBy(qb, meta2, Utils.asArray(childOrder), path, explicit, propAlias);
|
|
1170
1149
|
orderBy.push(...children);
|
|
1171
1150
|
continue;
|
|
@@ -1320,16 +1299,19 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
1320
1299
|
ret.push('*');
|
|
1321
1300
|
}
|
|
1322
1301
|
if (ret.length > 0 && !hasExplicitFields && addFormulas) {
|
|
1323
|
-
meta.props
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1302
|
+
for (const prop of meta.props) {
|
|
1303
|
+
if (lazyProps.includes(prop)) {
|
|
1304
|
+
continue;
|
|
1305
|
+
}
|
|
1306
|
+
if (prop.formula) {
|
|
1307
|
+
const a = this.platform.quoteIdentifier(alias);
|
|
1308
|
+
const aliased = this.platform.quoteIdentifier(prop.fieldNames[0]);
|
|
1309
|
+
ret.push(raw(`${prop.formula(a)} as ${aliased}`));
|
|
1310
|
+
}
|
|
1311
|
+
if (!prop.object && (prop.hasConvertToDatabaseValueSQL || prop.hasConvertToJSValueSQL)) {
|
|
1312
|
+
ret.push(prop.name);
|
|
1313
|
+
}
|
|
1314
|
+
}
|
|
1333
1315
|
}
|
|
1334
1316
|
// add joined relations after the root entity fields
|
|
1335
1317
|
if (joinedProps.length > 0) {
|
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)) {
|
|
@@ -1,16 +1,17 @@
|
|
|
1
|
-
import { type EntityMetadata, type EntityProperty, type Primary, type Transaction } from '@mikro-orm/core';
|
|
1
|
+
import { type Dictionary, type EntityMetadata, type EntityProperty, type Primary, type Transaction } from '@mikro-orm/core';
|
|
2
2
|
import { type AbstractSqlDriver } from './AbstractSqlDriver.js';
|
|
3
3
|
export declare class PivotCollectionPersister<Entity extends object> {
|
|
4
4
|
private readonly meta;
|
|
5
5
|
private readonly driver;
|
|
6
6
|
private readonly ctx?;
|
|
7
7
|
private readonly schema?;
|
|
8
|
+
private readonly loggerContext?;
|
|
8
9
|
private readonly platform;
|
|
9
10
|
private readonly inserts;
|
|
10
11
|
private readonly deletes;
|
|
11
12
|
private readonly batchSize;
|
|
12
13
|
private order;
|
|
13
|
-
constructor(meta: EntityMetadata<Entity>, driver: AbstractSqlDriver, ctx?: Transaction | undefined, schema?: string | undefined);
|
|
14
|
+
constructor(meta: EntityMetadata<Entity>, driver: AbstractSqlDriver, ctx?: Transaction | undefined, schema?: string | undefined, loggerContext?: Dictionary | undefined);
|
|
14
15
|
enqueueUpdate(prop: EntityProperty<Entity>, insertDiff: Primary<Entity>[][], deleteDiff: Primary<Entity>[][] | boolean, pks: Primary<Entity>[]): void;
|
|
15
16
|
private enqueueInsert;
|
|
16
17
|
private enqueueDelete;
|
|
@@ -38,16 +38,18 @@ export class PivotCollectionPersister {
|
|
|
38
38
|
driver;
|
|
39
39
|
ctx;
|
|
40
40
|
schema;
|
|
41
|
+
loggerContext;
|
|
41
42
|
platform;
|
|
42
43
|
inserts = new Map();
|
|
43
44
|
deletes = new Map();
|
|
44
45
|
batchSize;
|
|
45
46
|
order = 0;
|
|
46
|
-
constructor(meta, driver, ctx, schema) {
|
|
47
|
+
constructor(meta, driver, ctx, schema, loggerContext) {
|
|
47
48
|
this.meta = meta;
|
|
48
49
|
this.driver = driver;
|
|
49
50
|
this.ctx = ctx;
|
|
50
51
|
this.schema = schema;
|
|
52
|
+
this.loggerContext = loggerContext;
|
|
51
53
|
this.platform = this.driver.getPlatform();
|
|
52
54
|
this.batchSize = this.driver.config.get('batchSize');
|
|
53
55
|
}
|
|
@@ -99,6 +101,7 @@ export class PivotCollectionPersister {
|
|
|
99
101
|
await this.driver.nativeDelete(this.meta.className, cond, {
|
|
100
102
|
ctx: this.ctx,
|
|
101
103
|
schema: this.schema,
|
|
104
|
+
loggerContext: this.loggerContext,
|
|
102
105
|
});
|
|
103
106
|
}
|
|
104
107
|
}
|
|
@@ -118,13 +121,14 @@ export class PivotCollectionPersister {
|
|
|
118
121
|
schema: this.schema,
|
|
119
122
|
convertCustomTypes: false,
|
|
120
123
|
processCollections: false,
|
|
124
|
+
loggerContext: this.loggerContext,
|
|
121
125
|
});
|
|
122
126
|
}
|
|
123
127
|
/* v8 ignore start */
|
|
124
128
|
}
|
|
125
129
|
else {
|
|
126
130
|
await Utils.runSerial(items, item => {
|
|
127
|
-
return this.driver.createQueryBuilder(this.meta.className, this.ctx, 'write')
|
|
131
|
+
return this.driver.createQueryBuilder(this.meta.className, this.ctx, 'write', false, this.loggerContext)
|
|
128
132
|
.withSchema(this.schema)
|
|
129
133
|
.insert(item)
|
|
130
134
|
.execute('run', false);
|
package/README.md
CHANGED
|
@@ -11,7 +11,6 @@ TypeScript ORM for Node.js based on Data Mapper, [Unit of Work](https://mikro-or
|
|
|
11
11
|
[](https://discord.gg/w8bjxFHS7X)
|
|
12
12
|
[](https://www.npmjs.com/package/@mikro-orm/core)
|
|
13
13
|
[](https://coveralls.io/r/mikro-orm/mikro-orm?branch=master)
|
|
14
|
-
[](https://codeclimate.com/github/mikro-orm/mikro-orm/maintainability)
|
|
15
14
|
[](https://github.com/mikro-orm/mikro-orm/actions?workflow=tests)
|
|
16
15
|
|
|
17
16
|
## 🤔 Unit of What?
|
|
@@ -141,7 +140,7 @@ There is also auto-generated [CHANGELOG.md](CHANGELOG.md) file based on commit m
|
|
|
141
140
|
- [Composite and Foreign Keys as Primary Key](https://mikro-orm.io/docs/composite-keys)
|
|
142
141
|
- [Filters](https://mikro-orm.io/docs/filters)
|
|
143
142
|
- [Using `QueryBuilder`](https://mikro-orm.io/docs/query-builder)
|
|
144
|
-
- [
|
|
143
|
+
- [Populating relations](https://mikro-orm.io/docs/populating-relations)
|
|
145
144
|
- [Property Validation](https://mikro-orm.io/docs/property-validation)
|
|
146
145
|
- [Lifecycle Hooks](https://mikro-orm.io/docs/events#hooks)
|
|
147
146
|
- [Vanilla JS Support](https://mikro-orm.io/docs/usage-with-js)
|
|
@@ -5,6 +5,8 @@ export declare class MsSqlNativeQueryBuilder extends NativeQueryBuilder {
|
|
|
5
5
|
sql: string;
|
|
6
6
|
params: unknown[];
|
|
7
7
|
};
|
|
8
|
+
protected compileInsert(): void;
|
|
9
|
+
private appendOutputTable;
|
|
8
10
|
private compileUpsert;
|
|
9
11
|
protected compileSelect(): void;
|
|
10
12
|
protected addLockClause(): void;
|
|
@@ -12,6 +12,10 @@ export class MsSqlNativeQueryBuilder extends NativeQueryBuilder {
|
|
|
12
12
|
if (this.options.flags?.has(QueryFlag.IDENTITY_INSERT)) {
|
|
13
13
|
this.parts.push(`set identity_insert ${this.getTableName()} on;`);
|
|
14
14
|
}
|
|
15
|
+
const { prefix, suffix } = this.appendOutputTable();
|
|
16
|
+
if (prefix) {
|
|
17
|
+
this.parts.push(prefix);
|
|
18
|
+
}
|
|
15
19
|
if (this.options.comment) {
|
|
16
20
|
this.parts.push(...this.options.comment.map(comment => `/* ${comment} */`));
|
|
17
21
|
}
|
|
@@ -37,7 +41,11 @@ export class MsSqlNativeQueryBuilder extends NativeQueryBuilder {
|
|
|
37
41
|
this.compileTruncate();
|
|
38
42
|
break;
|
|
39
43
|
}
|
|
40
|
-
if (
|
|
44
|
+
if (suffix) {
|
|
45
|
+
this.parts[this.parts.length - 1] += ';';
|
|
46
|
+
this.parts.push(suffix);
|
|
47
|
+
}
|
|
48
|
+
else if ([QueryType.INSERT, QueryType.UPDATE, QueryType.DELETE].includes(this.type)) {
|
|
41
49
|
this.parts[this.parts.length - 1] += '; select @@rowcount;';
|
|
42
50
|
}
|
|
43
51
|
}
|
|
@@ -46,6 +54,37 @@ export class MsSqlNativeQueryBuilder extends NativeQueryBuilder {
|
|
|
46
54
|
}
|
|
47
55
|
return this.combineParts();
|
|
48
56
|
}
|
|
57
|
+
compileInsert() {
|
|
58
|
+
if (!this.options.data) {
|
|
59
|
+
throw new Error('No data provided');
|
|
60
|
+
}
|
|
61
|
+
this.parts.push('insert');
|
|
62
|
+
this.addHintComment();
|
|
63
|
+
this.parts.push(`into ${this.getTableName()}`);
|
|
64
|
+
if (Object.keys(this.options.data).length === 0) {
|
|
65
|
+
this.addOutputClause('inserted');
|
|
66
|
+
this.parts.push('default values');
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
const parts = this.processInsertData();
|
|
70
|
+
if (this.options.flags?.has(QueryFlag.OUTPUT_TABLE)) {
|
|
71
|
+
this.parts[this.parts.length - 2] += ' into #out ';
|
|
72
|
+
}
|
|
73
|
+
this.parts.push(parts.join(', '));
|
|
74
|
+
}
|
|
75
|
+
appendOutputTable() {
|
|
76
|
+
if (!this.options.flags?.has(QueryFlag.OUTPUT_TABLE)) {
|
|
77
|
+
return { prefix: '', suffix: '' };
|
|
78
|
+
}
|
|
79
|
+
const returningFields = this.options.returning;
|
|
80
|
+
const selections = returningFields
|
|
81
|
+
.map(field => `[t].${this.platform.quoteIdentifier(field)}`)
|
|
82
|
+
.join(',');
|
|
83
|
+
return {
|
|
84
|
+
prefix: `select top(0) ${selections} into #out from ${this.getTableName()} as t left join ${this.getTableName()} on 0 = 1;`,
|
|
85
|
+
suffix: `select ${selections} from #out as t; drop table #out`,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
49
88
|
compileUpsert() {
|
|
50
89
|
const clause = this.options.onConflict;
|
|
51
90
|
const dataAsArray = Utils.asArray(this.options.data);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mikro-orm/knex",
|
|
3
|
-
"version": "7.0.0-dev.
|
|
3
|
+
"version": "7.0.0-dev.23",
|
|
4
4
|
"description": "TypeScript ORM for Node.js based on Data Mapper, Unit of Work and Identity Map patterns. Supports MongoDB, MySQL, PostgreSQL and SQLite databases as well as usage with vanilla JavaScript.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -50,13 +50,13 @@
|
|
|
50
50
|
"access": "public"
|
|
51
51
|
},
|
|
52
52
|
"dependencies": {
|
|
53
|
-
"kysely": "0.28.
|
|
53
|
+
"kysely": "0.28.5",
|
|
54
54
|
"sqlstring": "2.3.3"
|
|
55
55
|
},
|
|
56
56
|
"devDependencies": {
|
|
57
|
-
"@mikro-orm/core": "^6.
|
|
57
|
+
"@mikro-orm/core": "^6.5.1"
|
|
58
58
|
},
|
|
59
59
|
"peerDependencies": {
|
|
60
|
-
"@mikro-orm/core": "7.0.0-dev.
|
|
60
|
+
"@mikro-orm/core": "7.0.0-dev.23"
|
|
61
61
|
}
|
|
62
62
|
}
|
package/query/CriteriaNode.js
CHANGED
|
@@ -66,7 +66,7 @@ export class CriteriaNode {
|
|
|
66
66
|
}
|
|
67
67
|
}
|
|
68
68
|
renameFieldToPK(qb) {
|
|
69
|
-
let joinAlias = qb.getAliasForJoinPath(this.getPath());
|
|
69
|
+
let joinAlias = qb.getAliasForJoinPath(this.getPath(), { matchPopulateJoins: true });
|
|
70
70
|
if (!joinAlias && this.parent && [ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(this.prop.kind) && this.prop.owner) {
|
|
71
71
|
joinAlias = qb.getAliasForJoinPath(this.parent.getPath());
|
|
72
72
|
return Utils.getPrimaryKeyHash(this.prop.joinColumns.map(col => `${joinAlias ?? qb.alias}.${col}`));
|
|
@@ -46,10 +46,14 @@ export class CriteriaNodeFactory {
|
|
|
46
46
|
static createObjectItemNode(metadata, entityName, node, payload, key, meta) {
|
|
47
47
|
const prop = meta?.properties[key];
|
|
48
48
|
const childEntity = prop && prop.kind !== ReferenceKind.SCALAR ? prop.type : entityName;
|
|
49
|
-
|
|
49
|
+
const isNotEmbedded = prop?.kind !== ReferenceKind.EMBEDDED;
|
|
50
|
+
if (isNotEmbedded && prop?.customType instanceof JsonType) {
|
|
50
51
|
return this.createScalarNode(metadata, childEntity, payload[key], node, key);
|
|
51
52
|
}
|
|
52
|
-
if (prop?.kind
|
|
53
|
+
if (prop?.kind === ReferenceKind.SCALAR && payload[key] != null && Object.keys(payload[key]).some(f => Utils.isGroupOperator(f))) {
|
|
54
|
+
throw ValidationError.cannotUseGroupOperatorsInsideScalars(entityName, prop.name, payload);
|
|
55
|
+
}
|
|
56
|
+
if (isNotEmbedded) {
|
|
53
57
|
return this.createNode(metadata, childEntity, payload[key], node, key);
|
|
54
58
|
}
|
|
55
59
|
if (payload[key] == null) {
|
|
@@ -66,11 +70,12 @@ export class CriteriaNodeFactory {
|
|
|
66
70
|
throw ValidationError.cannotUseOperatorsInsideEmbeddables(entityName, prop.name, payload);
|
|
67
71
|
}
|
|
68
72
|
const map = Object.keys(payload[key]).reduce((oo, k) => {
|
|
69
|
-
|
|
73
|
+
const embeddedProp = prop.embeddedProps[k] ?? Object.values(prop.embeddedProps).find(p => p.name === k);
|
|
74
|
+
if (!embeddedProp && !allowedOperators.includes(k)) {
|
|
70
75
|
throw ValidationError.invalidEmbeddableQuery(entityName, k, prop.type);
|
|
71
76
|
}
|
|
72
|
-
if (
|
|
73
|
-
oo[
|
|
77
|
+
if (embeddedProp) {
|
|
78
|
+
oo[embeddedProp.name] = payload[key][k];
|
|
74
79
|
}
|
|
75
80
|
else if (typeof payload[key][k] === 'object') {
|
|
76
81
|
oo[k] = JSON.stringify(payload[key][k]);
|
|
@@ -6,7 +6,8 @@ import { JoinType, QueryType } from './enums.js';
|
|
|
6
6
|
*/
|
|
7
7
|
export class ObjectCriteriaNode extends CriteriaNode {
|
|
8
8
|
process(qb, options) {
|
|
9
|
-
const
|
|
9
|
+
const matchPopulateJoins = options?.matchPopulateJoins || (this.prop && [ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(this.prop.kind));
|
|
10
|
+
const nestedAlias = qb.getAliasForJoinPath(this.getPath(), { ...options, matchPopulateJoins });
|
|
10
11
|
const ownerAlias = options?.alias || qb.alias;
|
|
11
12
|
const keys = Object.keys(this.payload);
|
|
12
13
|
let alias = options?.alias;
|
|
@@ -206,7 +207,23 @@ export class ObjectCriteriaNode extends CriteriaNode {
|
|
|
206
207
|
}
|
|
207
208
|
else {
|
|
208
209
|
const prev = qb._fields?.slice();
|
|
209
|
-
|
|
210
|
+
const toOneProperty = [ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(this.prop.kind);
|
|
211
|
+
const joinType = toOneProperty && !this.prop.nullable
|
|
212
|
+
? JoinType.innerJoin
|
|
213
|
+
: JoinType.leftJoin;
|
|
214
|
+
qb[method](field, nestedAlias, undefined, joinType, path);
|
|
215
|
+
// if the property is nullable, we need to use left join, so we mimic the inner join behaviour
|
|
216
|
+
// with an exclusive condition on the join columns:
|
|
217
|
+
// - if the owning column is null, the row is missing, we don't apply the filter
|
|
218
|
+
// - if the target column is not null, the row is matched, we apply the filter
|
|
219
|
+
if (toOneProperty && this.prop.nullable && options?.filter) {
|
|
220
|
+
qb.andWhere({
|
|
221
|
+
$or: [
|
|
222
|
+
{ [field]: null },
|
|
223
|
+
{ [nestedAlias + '.' + Utils.getPrimaryKeyHash(this.prop.referencedPKs)]: { $ne: null } },
|
|
224
|
+
],
|
|
225
|
+
});
|
|
226
|
+
}
|
|
210
227
|
if (!qb.hasFlag(QueryFlag.INFER_POPULATE)) {
|
|
211
228
|
qb._fields = prev;
|
|
212
229
|
}
|
package/query/QueryBuilder.d.ts
CHANGED
|
@@ -290,6 +290,11 @@ export declare class QueryBuilder<Entity extends object = AnyEntity, RootAlias e
|
|
|
290
290
|
processPopulateHint(): void;
|
|
291
291
|
private processPopulateWhere;
|
|
292
292
|
private mergeOnConditions;
|
|
293
|
+
/**
|
|
294
|
+
* When adding an inner join on a left joined relation, we need to nest them,
|
|
295
|
+
* otherwise the inner join could discard rows of the root table.
|
|
296
|
+
*/
|
|
297
|
+
private processNestedJoins;
|
|
293
298
|
private hasToManyJoins;
|
|
294
299
|
protected wrapPaginateSubQuery(meta: EntityMetadata): void;
|
|
295
300
|
private pruneExtraJoins;
|
package/query/QueryBuilder.js
CHANGED
|
@@ -179,10 +179,10 @@ export class QueryBuilder {
|
|
|
179
179
|
subquery = this.platform.formatQuery(rawFragment.sql, rawFragment.params);
|
|
180
180
|
field = field[0];
|
|
181
181
|
}
|
|
182
|
-
const prop = this.joinReference(field, alias, cond, type, path, schema, subquery);
|
|
182
|
+
const { prop, key } = this.joinReference(field, alias, cond, type, path, schema, subquery);
|
|
183
183
|
const [fromAlias] = this.helper.splitField(field);
|
|
184
184
|
if (subquery) {
|
|
185
|
-
this._joins[
|
|
185
|
+
this._joins[key].subquery = subquery;
|
|
186
186
|
}
|
|
187
187
|
const populate = this._joinedProps.get(fromAlias);
|
|
188
188
|
const item = { field: prop.name, strategy: LoadStrategy.JOINED, children: [] };
|
|
@@ -882,7 +882,8 @@ export class QueryBuilder {
|
|
|
882
882
|
if (field instanceof RawQueryFragment) {
|
|
883
883
|
field = this.platform.formatQuery(field.sql, field.params);
|
|
884
884
|
}
|
|
885
|
-
|
|
885
|
+
const key = `${this.alias}.${prop.name}#${alias}`;
|
|
886
|
+
this._joins[key] = {
|
|
886
887
|
prop,
|
|
887
888
|
alias,
|
|
888
889
|
type,
|
|
@@ -891,7 +892,7 @@ export class QueryBuilder {
|
|
|
891
892
|
subquery: field.toString(),
|
|
892
893
|
ownerAlias: this.alias,
|
|
893
894
|
};
|
|
894
|
-
return prop;
|
|
895
|
+
return { prop, key };
|
|
895
896
|
}
|
|
896
897
|
if (!subquery && type.includes('lateral')) {
|
|
897
898
|
throw new Error(`Lateral join can be used only with a sub-query.`);
|
|
@@ -920,6 +921,7 @@ export class QueryBuilder {
|
|
|
920
921
|
path ??= `${(Object.values(this._joins).find(j => j.alias === fromAlias)?.path ?? entityName)}.${prop.name}`;
|
|
921
922
|
if (prop.kind === ReferenceKind.ONE_TO_MANY) {
|
|
922
923
|
this._joins[aliasedName] = this.helper.joinOneToReference(prop, fromAlias, alias, type, cond, schema);
|
|
924
|
+
this._joins[aliasedName].path ??= path;
|
|
923
925
|
}
|
|
924
926
|
else if (prop.kind === ReferenceKind.MANY_TO_MANY) {
|
|
925
927
|
let pivotAlias = alias;
|
|
@@ -931,17 +933,18 @@ export class QueryBuilder {
|
|
|
931
933
|
const joins = this.helper.joinManyToManyReference(prop, fromAlias, alias, pivotAlias, type, cond, path, schema);
|
|
932
934
|
Object.assign(this._joins, joins);
|
|
933
935
|
this.createAlias(prop.pivotEntity, pivotAlias);
|
|
936
|
+
this._joins[aliasedName].path ??= path;
|
|
937
|
+
aliasedName = Object.keys(joins)[1];
|
|
934
938
|
}
|
|
935
939
|
else if (prop.kind === ReferenceKind.ONE_TO_ONE) {
|
|
936
940
|
this._joins[aliasedName] = this.helper.joinOneToReference(prop, fromAlias, alias, type, cond, schema);
|
|
941
|
+
this._joins[aliasedName].path ??= path;
|
|
937
942
|
}
|
|
938
943
|
else { // MANY_TO_ONE
|
|
939
944
|
this._joins[aliasedName] = this.helper.joinManyToOneReference(prop, fromAlias, alias, type, cond, schema);
|
|
945
|
+
this._joins[aliasedName].path ??= path;
|
|
940
946
|
}
|
|
941
|
-
|
|
942
|
-
this._joins[aliasedName].path = path;
|
|
943
|
-
}
|
|
944
|
-
return prop;
|
|
947
|
+
return { prop, key: aliasedName };
|
|
945
948
|
}
|
|
946
949
|
prepareFields(fields, type = 'where') {
|
|
947
950
|
const ret = [];
|
|
@@ -1116,6 +1119,7 @@ export class QueryBuilder {
|
|
|
1116
1119
|
const meta = this.mainAlias.metadata;
|
|
1117
1120
|
this.applyDiscriminatorCondition();
|
|
1118
1121
|
this.processPopulateHint();
|
|
1122
|
+
this.processNestedJoins();
|
|
1119
1123
|
if (meta && (this._fields?.includes('*') || this._fields?.includes(`${this.mainAlias.aliasName}.*`))) {
|
|
1120
1124
|
meta.props
|
|
1121
1125
|
.filter(prop => prop.formula && (!prop.lazy || this.flags.has(QueryFlag.INCLUDE_LAZY_FORMULAS)))
|
|
@@ -1195,7 +1199,7 @@ export class QueryBuilder {
|
|
|
1195
1199
|
if (typeof this[key] === 'object') {
|
|
1196
1200
|
const cond = CriteriaNodeFactory
|
|
1197
1201
|
.createNode(this.metadata, this.mainAlias.entityName, this[key])
|
|
1198
|
-
.process(this, { matchPopulateJoins: true, ignoreBranching: true, preferNoBranch: true });
|
|
1202
|
+
.process(this, { matchPopulateJoins: true, ignoreBranching: true, preferNoBranch: true, filter });
|
|
1199
1203
|
// there might be new joins created by processing the `populateWhere` object
|
|
1200
1204
|
joins = Object.values(this._joins);
|
|
1201
1205
|
this.mergeOnConditions(joins, cond, filter);
|
|
@@ -1236,6 +1240,29 @@ export class QueryBuilder {
|
|
|
1236
1240
|
}
|
|
1237
1241
|
}
|
|
1238
1242
|
}
|
|
1243
|
+
/**
|
|
1244
|
+
* When adding an inner join on a left joined relation, we need to nest them,
|
|
1245
|
+
* otherwise the inner join could discard rows of the root table.
|
|
1246
|
+
*/
|
|
1247
|
+
processNestedJoins() {
|
|
1248
|
+
if (this.flags.has(QueryFlag.DISABLE_NESTED_INNER_JOIN)) {
|
|
1249
|
+
return;
|
|
1250
|
+
}
|
|
1251
|
+
const joins = Object.values(this._joins);
|
|
1252
|
+
for (const join of joins) {
|
|
1253
|
+
if (join.type === JoinType.innerJoin) {
|
|
1254
|
+
const parentJoin = joins.find(j => j.alias === join.ownerAlias);
|
|
1255
|
+
// https://stackoverflow.com/a/56815807/3665878
|
|
1256
|
+
if (parentJoin?.type === JoinType.leftJoin || parentJoin?.type === JoinType.nestedLeftJoin) {
|
|
1257
|
+
const nested = (parentJoin.nested ??= new Set());
|
|
1258
|
+
join.type = join.type === JoinType.innerJoin
|
|
1259
|
+
? JoinType.nestedInnerJoin
|
|
1260
|
+
: JoinType.nestedLeftJoin;
|
|
1261
|
+
nested.add(join);
|
|
1262
|
+
}
|
|
1263
|
+
}
|
|
1264
|
+
}
|
|
1265
|
+
}
|
|
1239
1266
|
hasToManyJoins() {
|
|
1240
1267
|
return Object.values(this._joins).some(join => {
|
|
1241
1268
|
return [ReferenceKind.ONE_TO_MANY, ReferenceKind.MANY_TO_MANY].includes(join.prop.kind);
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { CriteriaNode } from './CriteriaNode.js';
|
|
2
|
-
import type {
|
|
2
|
+
import type { ICriteriaNodeProcessOptions, IQueryBuilder } from '../typings.js';
|
|
3
3
|
/**
|
|
4
4
|
* @internal
|
|
5
5
|
*/
|
|
6
6
|
export declare class ScalarCriteriaNode<T extends object> extends CriteriaNode<T> {
|
|
7
7
|
process(qb: IQueryBuilder<T>, options?: ICriteriaNodeProcessOptions): any;
|
|
8
|
-
willAutoJoin
|
|
9
|
-
shouldJoin
|
|
8
|
+
willAutoJoin(qb: IQueryBuilder<T>, alias?: string, options?: ICriteriaNodeProcessOptions): boolean;
|
|
9
|
+
private shouldJoin;
|
|
10
10
|
}
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import { ReferenceKind, Utils } from '@mikro-orm/core';
|
|
2
2
|
import { CriteriaNode } from './CriteriaNode.js';
|
|
3
|
-
import { JoinType } from './enums.js';
|
|
3
|
+
import { JoinType, QueryType } from './enums.js';
|
|
4
4
|
import { QueryBuilder } from './QueryBuilder.js';
|
|
5
5
|
/**
|
|
6
6
|
* @internal
|
|
7
7
|
*/
|
|
8
8
|
export class ScalarCriteriaNode extends CriteriaNode {
|
|
9
9
|
process(qb, options) {
|
|
10
|
-
|
|
10
|
+
const matchPopulateJoins = options?.matchPopulateJoins || (this.prop && [ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(this.prop.kind));
|
|
11
|
+
const nestedAlias = qb.getAliasForJoinPath(this.getPath(), { ...options, matchPopulateJoins });
|
|
12
|
+
if (this.shouldJoin(qb, nestedAlias)) {
|
|
11
13
|
const path = this.getPath();
|
|
12
14
|
const parentPath = this.parent.getPath(); // the parent is always there, otherwise `shouldJoin` would return `false`
|
|
13
15
|
const nestedAlias = qb.getAliasForJoinPath(path) || qb.getNextAlias(this.prop?.pivotTable ?? this.entityName);
|
|
@@ -31,10 +33,10 @@ export class ScalarCriteriaNode extends CriteriaNode {
|
|
|
31
33
|
return this.payload;
|
|
32
34
|
}
|
|
33
35
|
willAutoJoin(qb, alias, options) {
|
|
34
|
-
return this.shouldJoin();
|
|
36
|
+
return this.shouldJoin(qb, alias);
|
|
35
37
|
}
|
|
36
|
-
shouldJoin() {
|
|
37
|
-
if (!this.parent || !this.prop) {
|
|
38
|
+
shouldJoin(qb, nestedAlias) {
|
|
39
|
+
if (!this.parent || !this.prop || (nestedAlias && [QueryType.SELECT, QueryType.COUNT].includes(qb.type ?? QueryType.SELECT))) {
|
|
38
40
|
return false;
|
|
39
41
|
}
|
|
40
42
|
switch (this.prop.kind) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type Configuration, type DeferMode, type Dictionary, type EntityMetadata, type EntityProperty, type NamingStrategy } from '@mikro-orm/core';
|
|
1
|
+
import { type Configuration, type DeferMode, type Dictionary, type EntityMetadata, type EntityProperty, type NamingStrategy, type IndexCallback } from '@mikro-orm/core';
|
|
2
2
|
import type { SchemaHelper } from './SchemaHelper.js';
|
|
3
3
|
import type { CheckDef, Column, ForeignKey, IndexDef } from '../typings.js';
|
|
4
4
|
import type { AbstractSqlPlatform } from '../AbstractSqlPlatform.js';
|
|
@@ -54,12 +54,13 @@ export declare class DatabaseTable {
|
|
|
54
54
|
private getPropertyTypeForForeignKey;
|
|
55
55
|
private getPropertyTypeForColumn;
|
|
56
56
|
private getPropertyDefaultValue;
|
|
57
|
+
private processIndexExpression;
|
|
57
58
|
addIndex(meta: EntityMetadata, index: {
|
|
58
|
-
properties
|
|
59
|
+
properties?: string | string[];
|
|
59
60
|
name?: string;
|
|
60
61
|
type?: string;
|
|
61
|
-
expression?: string
|
|
62
|
-
deferMode?: DeferMode
|
|
62
|
+
expression?: string | IndexCallback<any>;
|
|
63
|
+
deferMode?: DeferMode | `${DeferMode}`;
|
|
63
64
|
options?: Dictionary;
|
|
64
65
|
}, type: 'index' | 'unique' | 'primary'): void;
|
|
65
66
|
addCheck(check: CheckDef): void;
|
package/schema/DatabaseTable.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Cascade, DecimalType, EntitySchema, ReferenceKind, t, Type, UnknownType, Utils, } from '@mikro-orm/core';
|
|
1
|
+
import { Cascade, DecimalType, EntitySchema, ReferenceKind, t, Type, UnknownType, Utils, RawQueryFragment, } from '@mikro-orm/core';
|
|
2
2
|
/**
|
|
3
3
|
* @internal
|
|
4
4
|
*/
|
|
@@ -115,6 +115,7 @@ export class DatabaseTable {
|
|
|
115
115
|
localTableName: this.getShortestName(false),
|
|
116
116
|
referencedColumnNames: prop.referencedColumnNames,
|
|
117
117
|
referencedTableName: schema ? `${schema}.${prop.referencedTableName}` : prop.referencedTableName,
|
|
118
|
+
createForeignKeyConstraint: prop.createForeignKeyConstraint,
|
|
118
119
|
};
|
|
119
120
|
const cascade = prop.cascade.includes(Cascade.REMOVE) || prop.cascade.includes(Cascade.ALL);
|
|
120
121
|
if (prop.deleteRule || cascade || prop.nullable) {
|
|
@@ -700,6 +701,23 @@ export class DatabaseTable {
|
|
|
700
701
|
}
|
|
701
702
|
return '' + val;
|
|
702
703
|
}
|
|
704
|
+
processIndexExpression(indexName, expression, meta) {
|
|
705
|
+
if (expression instanceof Function) {
|
|
706
|
+
const table = {
|
|
707
|
+
name: this.name,
|
|
708
|
+
schema: this.schema,
|
|
709
|
+
toString() {
|
|
710
|
+
if (this.schema) {
|
|
711
|
+
return `${this.schema}.${this.name}`;
|
|
712
|
+
}
|
|
713
|
+
return this.name;
|
|
714
|
+
},
|
|
715
|
+
};
|
|
716
|
+
const exp = expression(table, meta.createColumnMappingObject(), indexName);
|
|
717
|
+
return exp instanceof RawQueryFragment ? this.platform.formatQuery(exp.sql, exp.params) : exp;
|
|
718
|
+
}
|
|
719
|
+
return expression;
|
|
720
|
+
}
|
|
703
721
|
addIndex(meta, index, type) {
|
|
704
722
|
const properties = Utils.unique(Utils.flatten(Utils.asArray(index.properties).map(prop => {
|
|
705
723
|
const parts = prop.split('.');
|
|
@@ -741,7 +759,7 @@ export class DatabaseTable {
|
|
|
741
759
|
primary: type === 'primary',
|
|
742
760
|
unique: type !== 'index',
|
|
743
761
|
type: index.type,
|
|
744
|
-
expression: index.expression,
|
|
762
|
+
expression: this.processIndexExpression(name, index.expression, meta),
|
|
745
763
|
options: index.options,
|
|
746
764
|
deferMode: index.deferMode,
|
|
747
765
|
});
|
package/schema/SchemaHelper.js
CHANGED
|
@@ -263,7 +263,12 @@ export class SchemaHelper {
|
|
|
263
263
|
if (column.autoincrement && !column.generated && !compositePK && (!changedProperties || changedProperties.has('autoincrement') || changedProperties.has('type'))) {
|
|
264
264
|
Utils.runIfNotEmpty(() => col.push('primary key'), primaryKey && column.primary);
|
|
265
265
|
}
|
|
266
|
-
|
|
266
|
+
if (useDefault) {
|
|
267
|
+
// https://dev.mysql.com/doc/refman/9.0/en/data-type-defaults.html
|
|
268
|
+
const needsExpression = ['blob', 'text', 'json', 'point', 'linestring', 'polygon', 'multipoint', 'multilinestring', 'multipolygon', 'geometrycollection'].some(type => column.type.toLowerCase().startsWith(type));
|
|
269
|
+
const defaultSql = needsExpression && !column.default.startsWith('(') ? `(${column.default})` : column.default;
|
|
270
|
+
col.push(`default ${defaultSql}`);
|
|
271
|
+
}
|
|
267
272
|
Utils.runIfNotEmpty(() => col.push(column.extra), column.extra);
|
|
268
273
|
Utils.runIfNotEmpty(() => col.push(`comment ${this.platform.quoteValue(column.comment)}`), column.comment);
|
|
269
274
|
return col.join(' ');
|
|
@@ -421,7 +426,7 @@ export class SchemaHelper {
|
|
|
421
426
|
return `alter table ${table.getQuotedName()} comment = ${this.platform.quoteValue(comment ?? '')}`;
|
|
422
427
|
}
|
|
423
428
|
createForeignKey(table, foreignKey, alterTable = true, inline = false) {
|
|
424
|
-
if (!this.options.createForeignKeyConstraints) {
|
|
429
|
+
if (!this.options.createForeignKeyConstraints || !foreignKey.createForeignKeyConstraint) {
|
|
425
430
|
return '';
|
|
426
431
|
}
|
|
427
432
|
const constraintName = this.quote(foreignKey.constraintName);
|
|
@@ -284,7 +284,6 @@ export class SqlSchemaGenerator extends AbstractSchemaGenerator {
|
|
|
284
284
|
name ??= this.config.get('dbName');
|
|
285
285
|
const sql = this.helper.getCreateDatabaseSQL('' + this.platform.quoteIdentifier(name));
|
|
286
286
|
if (sql) {
|
|
287
|
-
// console.log(sql);
|
|
288
287
|
await this.execute(sql);
|
|
289
288
|
}
|
|
290
289
|
this.config.set('dbName', name);
|
|
@@ -318,12 +317,10 @@ export class SqlSchemaGenerator extends AbstractSchemaGenerator {
|
|
|
318
317
|
if (this.platform.supportsMultipleStatements()) {
|
|
319
318
|
for (const group of groups) {
|
|
320
319
|
const query = group.join('\n');
|
|
321
|
-
// console.log(query);
|
|
322
320
|
await this.driver.execute(query);
|
|
323
321
|
}
|
|
324
322
|
return;
|
|
325
323
|
}
|
|
326
|
-
// console.log(groups);
|
|
327
324
|
await Utils.runSerial(groups.flat(), line => this.driver.execute(line));
|
|
328
325
|
}
|
|
329
326
|
async dropTableIfExists(name, schema) {
|
package/typings.d.ts
CHANGED
|
@@ -59,6 +59,7 @@ export interface ForeignKey {
|
|
|
59
59
|
updateRule?: string;
|
|
60
60
|
deleteRule?: string;
|
|
61
61
|
deferMode?: DeferMode;
|
|
62
|
+
createForeignKeyConstraint: boolean;
|
|
62
63
|
}
|
|
63
64
|
export interface IndexDef {
|
|
64
65
|
columnNames: string[];
|
|
@@ -74,7 +75,7 @@ export interface IndexDef {
|
|
|
74
75
|
storageEngineIndexType?: 'hash' | 'btree';
|
|
75
76
|
predicate?: string;
|
|
76
77
|
}>;
|
|
77
|
-
deferMode?: DeferMode
|
|
78
|
+
deferMode?: DeferMode | `${DeferMode}`;
|
|
78
79
|
}
|
|
79
80
|
export interface CheckDef<T = unknown> {
|
|
80
81
|
name: string;
|
|
@@ -171,6 +172,7 @@ export interface ICriteriaNodeProcessOptions {
|
|
|
171
172
|
ignoreBranching?: boolean;
|
|
172
173
|
preferNoBranch?: boolean;
|
|
173
174
|
type?: 'orderBy';
|
|
175
|
+
filter?: boolean;
|
|
174
176
|
}
|
|
175
177
|
export interface ICriteriaNode<T extends object> {
|
|
176
178
|
readonly entityName: string;
|