@mikro-orm/knex 6.4.17-dev.9 → 6.4.17-dev.91
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 +31 -10
- package/AbstractSqlDriver.js +120 -138
- package/AbstractSqlPlatform.d.ts +10 -1
- package/AbstractSqlPlatform.js +21 -0
- package/PivotCollectionPersister.d.ts +3 -2
- package/PivotCollectionPersister.js +6 -2
- package/README.md +1 -2
- package/dialects/mysql/MySqlPlatform.d.ts +5 -1
- package/dialects/mysql/MySqlPlatform.js +14 -0
- package/dialects/mysql/MySqlSchemaHelper.js +8 -1
- package/dialects/sqlite/BaseSqlitePlatform.d.ts +5 -1
- package/dialects/sqlite/BaseSqlitePlatform.js +3 -0
- package/index.mjs +4 -0
- package/package.json +3 -3
- package/query/CriteriaNode.js +1 -1
- package/query/CriteriaNodeFactory.js +7 -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 +6 -4
- package/schema/DatabaseTable.d.ts +5 -4
- package/schema/DatabaseTable.js +13 -1
- package/typings.d.ts +2 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type Knex } from 'knex';
|
|
2
|
-
import { Connection, type AnyEntity, type Configuration, type ConnectionOptions, type EntityData, type IsolationLevel, type QueryResult, type Transaction, type TransactionEventBroadcaster, type LoggingOptions } from '@mikro-orm/core';
|
|
2
|
+
import { Connection, type AnyEntity, type Configuration, type ConnectionOptions, type EntityData, type IsolationLevel, type QueryResult, type Transaction, type TransactionEventBroadcaster, type LogContext, type LoggingOptions } from '@mikro-orm/core';
|
|
3
3
|
import type { AbstractSqlPlatform } from './AbstractSqlPlatform';
|
|
4
4
|
export declare abstract class AbstractSqlConnection extends Connection {
|
|
5
5
|
private static __patched;
|
|
@@ -33,15 +33,17 @@ export declare abstract class AbstractSqlConnection extends Connection {
|
|
|
33
33
|
readOnly?: boolean;
|
|
34
34
|
ctx?: Knex.Transaction;
|
|
35
35
|
eventBroadcaster?: TransactionEventBroadcaster;
|
|
36
|
+
loggerContext?: LogContext;
|
|
36
37
|
}): Promise<T>;
|
|
37
38
|
begin(options?: {
|
|
38
39
|
isolationLevel?: IsolationLevel;
|
|
39
40
|
readOnly?: boolean;
|
|
40
41
|
ctx?: Knex.Transaction;
|
|
41
42
|
eventBroadcaster?: TransactionEventBroadcaster;
|
|
43
|
+
loggerContext?: LogContext;
|
|
42
44
|
}): Promise<Knex.Transaction>;
|
|
43
|
-
commit(ctx: Knex.Transaction, eventBroadcaster?: TransactionEventBroadcaster): Promise<void>;
|
|
44
|
-
rollback(ctx: Knex.Transaction, eventBroadcaster?: TransactionEventBroadcaster): Promise<void>;
|
|
45
|
+
commit(ctx: Knex.Transaction, eventBroadcaster?: TransactionEventBroadcaster, loggerContext?: LogContext): Promise<void>;
|
|
46
|
+
rollback(ctx: Knex.Transaction, eventBroadcaster?: TransactionEventBroadcaster, loggerContext?: LogContext): Promise<void>;
|
|
45
47
|
execute<T extends QueryResult | EntityData<AnyEntity> | EntityData<AnyEntity>[] = EntityData<AnyEntity>[]>(queryOrKnex: string | Knex.QueryBuilder | Knex.Raw, params?: unknown[], method?: 'all' | 'get' | 'run', ctx?: Transaction, loggerContext?: LoggingOptions): Promise<T>;
|
|
46
48
|
/**
|
|
47
49
|
* Execute raw SQL queries from file
|
package/AbstractSqlConnection.js
CHANGED
|
@@ -56,11 +56,11 @@ class AbstractSqlConnection extends core_1.Connection {
|
|
|
56
56
|
const trx = await this.begin(options);
|
|
57
57
|
try {
|
|
58
58
|
const ret = await cb(trx);
|
|
59
|
-
await this.commit(trx, options.eventBroadcaster);
|
|
59
|
+
await this.commit(trx, options.eventBroadcaster, options.loggerContext);
|
|
60
60
|
return ret;
|
|
61
61
|
}
|
|
62
62
|
catch (error) {
|
|
63
|
-
await this.rollback(trx, options.eventBroadcaster);
|
|
63
|
+
await this.rollback(trx, options.eventBroadcaster, options.loggerContext);
|
|
64
64
|
throw error;
|
|
65
65
|
}
|
|
66
66
|
}
|
|
@@ -72,6 +72,19 @@ class AbstractSqlConnection extends core_1.Connection {
|
|
|
72
72
|
isolationLevel: options.isolationLevel,
|
|
73
73
|
readOnly: options.readOnly,
|
|
74
74
|
});
|
|
75
|
+
if (options.ctx) {
|
|
76
|
+
const ctx = options.ctx;
|
|
77
|
+
ctx.index ??= 0;
|
|
78
|
+
const savepointName = `trx${ctx.index + 1}`;
|
|
79
|
+
Reflect.defineProperty(trx, 'index', { value: ctx.index + 1 });
|
|
80
|
+
Reflect.defineProperty(trx, 'savepointName', { value: savepointName });
|
|
81
|
+
this.logQuery(this.platform.getSavepointSQL(savepointName), options.loggerContext);
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
for (const query of this.platform.getBeginTransactionSQL(options)) {
|
|
85
|
+
this.logQuery(query, options.loggerContext);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
75
88
|
if (!options.ctx) {
|
|
76
89
|
await options.eventBroadcaster?.dispatchEvent(core_1.EventType.afterTransactionStart, trx);
|
|
77
90
|
}
|
|
@@ -80,23 +93,35 @@ class AbstractSqlConnection extends core_1.Connection {
|
|
|
80
93
|
}
|
|
81
94
|
return trx;
|
|
82
95
|
}
|
|
83
|
-
async commit(ctx, eventBroadcaster) {
|
|
96
|
+
async commit(ctx, eventBroadcaster, loggerContext) {
|
|
84
97
|
const runTrxHooks = isRootTransaction(ctx);
|
|
85
98
|
if (runTrxHooks) {
|
|
86
99
|
await eventBroadcaster?.dispatchEvent(core_1.EventType.beforeTransactionCommit, ctx);
|
|
87
100
|
}
|
|
88
101
|
ctx.commit();
|
|
89
102
|
await ctx.executionPromise; // https://github.com/knex/knex/issues/3847#issuecomment-626330453
|
|
103
|
+
if ('savepointName' in ctx) {
|
|
104
|
+
this.logQuery(this.platform.getReleaseSavepointSQL(ctx.savepointName), loggerContext);
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
this.logQuery(this.platform.getCommitTransactionSQL(), loggerContext);
|
|
108
|
+
}
|
|
90
109
|
if (runTrxHooks) {
|
|
91
110
|
await eventBroadcaster?.dispatchEvent(core_1.EventType.afterTransactionCommit, ctx);
|
|
92
111
|
}
|
|
93
112
|
}
|
|
94
|
-
async rollback(ctx, eventBroadcaster) {
|
|
113
|
+
async rollback(ctx, eventBroadcaster, loggerContext) {
|
|
95
114
|
const runTrxHooks = isRootTransaction(ctx);
|
|
96
115
|
if (runTrxHooks) {
|
|
97
116
|
await eventBroadcaster?.dispatchEvent(core_1.EventType.beforeTransactionRollback, ctx);
|
|
98
117
|
}
|
|
99
118
|
await ctx.rollback();
|
|
119
|
+
if ('savepointName' in ctx) {
|
|
120
|
+
this.logQuery(this.platform.getRollbackToSavepointSQL(ctx.savepointName), loggerContext);
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
this.logQuery(this.platform.getRollbackTransactionSQL(), loggerContext);
|
|
124
|
+
}
|
|
100
125
|
if (runTrxHooks) {
|
|
101
126
|
await eventBroadcaster?.dispatchEvent(core_1.EventType.afterTransactionRollback, ctx);
|
|
102
127
|
}
|
|
@@ -107,7 +132,7 @@ class AbstractSqlConnection extends core_1.Connection {
|
|
|
107
132
|
ctx ??= (queryOrKnex.client.transacting ? queryOrKnex : null);
|
|
108
133
|
const q = queryOrKnex.toSQL();
|
|
109
134
|
queryOrKnex = q.sql;
|
|
110
|
-
params = q.bindings;
|
|
135
|
+
params = q.bindings ?? [];
|
|
111
136
|
}
|
|
112
137
|
queryOrKnex = this.config.get('onQuery')(queryOrKnex, params);
|
|
113
138
|
const formatted = this.platform.formatQuery(queryOrKnex, params);
|
|
@@ -141,11 +166,7 @@ class AbstractSqlConnection extends core_1.Connection {
|
|
|
141
166
|
return driverOptions;
|
|
142
167
|
}
|
|
143
168
|
return (0, knex_1.knex)(this.getKnexOptions(type))
|
|
144
|
-
.on('query', data =>
|
|
145
|
-
if (!data.__knexQueryUid) {
|
|
146
|
-
this.logQuery(data.sql.toLowerCase().replace(/;$/, ''));
|
|
147
|
-
}
|
|
148
|
-
});
|
|
169
|
+
.on('query', data => data);
|
|
149
170
|
}
|
|
150
171
|
getKnexOptions(type) {
|
|
151
172
|
const config = core_1.Utils.mergeConfig({
|
package/AbstractSqlDriver.js
CHANGED
|
@@ -333,6 +333,9 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
333
333
|
if (meta && !core_1.Utils.isEmpty(populate)) {
|
|
334
334
|
this.buildFields(meta, populate, joinedProps, qb, qb.alias, options, true);
|
|
335
335
|
}
|
|
336
|
+
if (options.em) {
|
|
337
|
+
await qb.applyJoinedFilters(options.em, options.filters);
|
|
338
|
+
}
|
|
336
339
|
return this.rethrow(qb.getCount());
|
|
337
340
|
}
|
|
338
341
|
async nativeInsert(entityName, data, options = {}) {
|
|
@@ -340,7 +343,7 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
340
343
|
const meta = this.metadata.find(entityName);
|
|
341
344
|
const collections = this.extractManyToMany(entityName, data);
|
|
342
345
|
const pks = meta?.primaryKeys ?? [this.config.getNamingStrategy().referenceColumnName()];
|
|
343
|
-
const qb = this.createQueryBuilder(entityName, options.ctx, 'write', options.convertCustomTypes).withSchema(this.getSchemaName(meta, options));
|
|
346
|
+
const qb = this.createQueryBuilder(entityName, options.ctx, 'write', options.convertCustomTypes, options.loggerContext).withSchema(this.getSchemaName(meta, options));
|
|
344
347
|
const res = await this.rethrow(qb.insert(data).execute('run', false));
|
|
345
348
|
res.row = res.row || {};
|
|
346
349
|
let pk;
|
|
@@ -401,14 +404,14 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
401
404
|
value = this.mapDataToFieldNames(value, false, prop.embeddedProps, options.convertCustomTypes);
|
|
402
405
|
}
|
|
403
406
|
}
|
|
404
|
-
if (options.convertCustomTypes && prop.customType) {
|
|
405
|
-
params.push(prop.customType.convertToDatabaseValue(value, this.platform, { key: prop.name, mode: 'query-data' }));
|
|
406
|
-
return;
|
|
407
|
-
}
|
|
408
407
|
if (typeof value === 'undefined' && this.platform.usesDefaultKeyword()) {
|
|
409
408
|
params.push((0, core_1.raw)('default'));
|
|
410
409
|
return;
|
|
411
410
|
}
|
|
411
|
+
if (options.convertCustomTypes && prop.customType) {
|
|
412
|
+
params.push(prop.customType.convertToDatabaseValue(value, this.platform, { key: prop.name, mode: 'query-data' }));
|
|
413
|
+
return;
|
|
414
|
+
}
|
|
412
415
|
params.push(value);
|
|
413
416
|
};
|
|
414
417
|
if (fields.length > 0 || this.platform.usesDefaultKeyword()) {
|
|
@@ -467,7 +470,7 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
467
470
|
if (transform) {
|
|
468
471
|
sql = transform(sql);
|
|
469
472
|
}
|
|
470
|
-
const res = await this.execute(sql, params, 'run', options.ctx);
|
|
473
|
+
const res = await this.execute(sql, params, 'run', options.ctx, options.loggerContext);
|
|
471
474
|
let pk;
|
|
472
475
|
/* istanbul ignore next */
|
|
473
476
|
if (pks.length > 1) { // owner has composite pk
|
|
@@ -495,7 +498,7 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
495
498
|
where = { [meta?.primaryKeys[0] ?? pks[0]]: where };
|
|
496
499
|
}
|
|
497
500
|
if (core_1.Utils.hasObjectKeys(data)) {
|
|
498
|
-
const qb = this.createQueryBuilder(entityName, options.ctx, 'write', options.convertCustomTypes)
|
|
501
|
+
const qb = this.createQueryBuilder(entityName, options.ctx, 'write', options.convertCustomTypes, options.loggerContext)
|
|
499
502
|
.withSchema(this.getSchemaName(meta, options));
|
|
500
503
|
if (options.upsert) {
|
|
501
504
|
/* istanbul ignore next */
|
|
@@ -534,7 +537,7 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
534
537
|
const meta = this.metadata.get(entityName);
|
|
535
538
|
if (options.upsert) {
|
|
536
539
|
const uniqueFields = options.onConflictFields ?? (core_1.Utils.isPlainObject(where[0]) ? Object.keys(where[0]).flatMap(key => core_1.Utils.splitPrimaryKeys(key)) : meta.primaryKeys);
|
|
537
|
-
const qb = this.createQueryBuilder(entityName, options.ctx, 'write', options.convertCustomTypes).withSchema(this.getSchemaName(meta, options));
|
|
540
|
+
const qb = this.createQueryBuilder(entityName, options.ctx, 'write', options.convertCustomTypes, options.loggerContext).withSchema(this.getSchemaName(meta, options));
|
|
538
541
|
const returning = (0, core_1.getOnConflictReturningFields)(meta, data[0], uniqueFields, options);
|
|
539
542
|
qb.insert(data)
|
|
540
543
|
.onConflict(uniqueFields)
|
|
@@ -647,7 +650,7 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
647
650
|
/* istanbul ignore next */
|
|
648
651
|
sql += returningFields.length > 0 ? ` returning ${returningFields.map(field => this.platform.quoteIdentifier(field)).join(', ')}` : '';
|
|
649
652
|
}
|
|
650
|
-
const res = await this.rethrow(this.execute(sql, params, 'run', options.ctx));
|
|
653
|
+
const res = await this.rethrow(this.execute(sql, params, 'run', options.ctx, options.loggerContext));
|
|
651
654
|
for (let i = 0; i < collections.length; i++) {
|
|
652
655
|
await this.processManyToMany(meta, where[i], collections[i], false, options);
|
|
653
656
|
}
|
|
@@ -659,7 +662,7 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
659
662
|
if (core_1.Utils.isPrimaryKey(where) && pks.length === 1) {
|
|
660
663
|
where = { [pks[0]]: where };
|
|
661
664
|
}
|
|
662
|
-
const qb = this.createQueryBuilder(entityName, options.ctx, 'write', false).delete(where).withSchema(this.getSchemaName(meta, options));
|
|
665
|
+
const qb = this.createQueryBuilder(entityName, options.ctx, 'write', false, options.loggerContext).delete(where).withSchema(this.getSchemaName(meta, options));
|
|
663
666
|
return this.rethrow(qb.execute('run', false));
|
|
664
667
|
}
|
|
665
668
|
/**
|
|
@@ -747,7 +750,7 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
747
750
|
schema = this.config.get('schema');
|
|
748
751
|
}
|
|
749
752
|
const tableName = `${schema ?? '_'}.${pivotMeta.tableName}`;
|
|
750
|
-
const persister = groups[tableName] ??= new PivotCollectionPersister_1.PivotCollectionPersister(pivotMeta, this, options?.ctx, schema);
|
|
753
|
+
const persister = groups[tableName] ??= new PivotCollectionPersister_1.PivotCollectionPersister(pivotMeta, this, options?.ctx, schema, options?.loggerContext);
|
|
751
754
|
persister.enqueueUpdate(coll.property, insertDiff, deleteDiff, pks);
|
|
752
755
|
}
|
|
753
756
|
for (const persister of core_1.Utils.values(groups)) {
|
|
@@ -755,112 +758,64 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
755
758
|
}
|
|
756
759
|
}
|
|
757
760
|
async loadFromPivotTable(prop, owners, where = {}, orderBy, ctx, options, pivotJoin) {
|
|
761
|
+
if (owners.length === 0) {
|
|
762
|
+
return {};
|
|
763
|
+
}
|
|
758
764
|
const pivotMeta = this.metadata.find(prop.pivotEntity);
|
|
759
765
|
const pivotProp1 = pivotMeta.relations[prop.owner ? 1 : 0];
|
|
760
766
|
const pivotProp2 = pivotMeta.relations[prop.owner ? 0 : 1];
|
|
761
767
|
const ownerMeta = this.metadata.find(pivotProp2.type);
|
|
762
|
-
options = { ...options };
|
|
763
|
-
const qb = this.createQueryBuilder(prop.pivotEntity, ctx, options.connectionType, undefined, options?.logging)
|
|
764
|
-
.withSchema(this.getSchemaName(pivotMeta, options))
|
|
765
|
-
.indexHint(options.indexHint)
|
|
766
|
-
.comment(options.comments)
|
|
767
|
-
.hintComment(options.hintComments);
|
|
768
|
-
const pivotAlias = qb.alias;
|
|
769
|
-
const pivotKey = pivotProp2.joinColumns.map(column => `${pivotAlias}.${column}`).join(core_1.Utils.PK_SEPARATOR);
|
|
770
768
|
const cond = {
|
|
771
|
-
[
|
|
769
|
+
[pivotProp2.name]: { $in: ownerMeta.compositePK ? owners : owners.map(o => o[0]) },
|
|
772
770
|
};
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
}
|
|
780
|
-
|
|
781
|
-
const
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
const
|
|
785
|
-
|
|
786
|
-
...
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
const additionalFields = [];
|
|
796
|
-
for (const field of targetFields) {
|
|
797
|
-
const f = field.toString();
|
|
798
|
-
additionalFields.push(f.includes('.') ? field : `${targetAlias}.${f}`);
|
|
799
|
-
if (core_1.RawQueryFragment.isKnownFragment(field)) {
|
|
800
|
-
qb.rawFragments.add(f);
|
|
801
|
-
}
|
|
802
|
-
}
|
|
803
|
-
fields.unshift(...additionalFields);
|
|
804
|
-
// we need to handle 1:1 owner auto-joins explicitly, as the QB type is the pivot table, not the target
|
|
805
|
-
populate.forEach(hint => {
|
|
806
|
-
const alias = qb.getNextAlias(prop.targetMeta.tableName);
|
|
807
|
-
qb.leftJoin(`${targetAlias}.${hint.field}`, alias);
|
|
808
|
-
// eslint-disable-next-line dot-notation
|
|
809
|
-
for (const join of Object.values(qb['_joins'])) {
|
|
810
|
-
const [propName] = hint.field.split(':', 2);
|
|
811
|
-
if (join.alias === alias && join.prop.name === propName) {
|
|
812
|
-
fields.push(...qb.helper.mapJoinColumns(qb.type, join));
|
|
813
|
-
}
|
|
814
|
-
}
|
|
815
|
-
});
|
|
816
|
-
}
|
|
817
|
-
qb.select(fields)
|
|
818
|
-
.where({ [pivotProp1.name]: where })
|
|
819
|
-
.orderBy(orderBy)
|
|
820
|
-
.setLockMode(options.lockMode, options.lockTableAliases);
|
|
821
|
-
if (owners.length === 1 && (options.offset != null || options.limit != null)) {
|
|
822
|
-
qb.limit(options.limit, options.offset);
|
|
823
|
-
}
|
|
824
|
-
const res = owners.length ? await this.rethrow(qb.execute('all', { mergeResults: false, mapResults: false })) : [];
|
|
825
|
-
const tmp = {};
|
|
826
|
-
const items = res.map((row) => {
|
|
827
|
-
const root = super.mapResult(row, prop.targetMeta);
|
|
828
|
-
this.mapJoinedProps(root, prop.targetMeta, populate, qb, root, tmp, pivotMeta.className + '.' + pivotProp1.name);
|
|
829
|
-
return root;
|
|
771
|
+
if (!core_1.Utils.isEmpty(where)) {
|
|
772
|
+
cond[pivotProp1.name] = { ...where };
|
|
773
|
+
}
|
|
774
|
+
where = cond;
|
|
775
|
+
const populateField = pivotJoin ? `${pivotProp1.name}:ref` : pivotProp1.name;
|
|
776
|
+
const populate = this.autoJoinOneToOneOwner(prop.targetMeta, options?.populate ?? [], options?.fields);
|
|
777
|
+
const childFields = !core_1.Utils.isEmpty(options?.fields) ? options.fields.map(f => `${pivotProp1.name}.${f}`) : [];
|
|
778
|
+
const childExclude = !core_1.Utils.isEmpty(options?.exclude) ? options.exclude.map(f => `${pivotProp1.name}.${f}`) : [];
|
|
779
|
+
const fields = pivotJoin
|
|
780
|
+
? [pivotProp1.name, pivotProp2.name]
|
|
781
|
+
: [pivotProp1.name, pivotProp2.name, ...childFields];
|
|
782
|
+
const res = await this.find(pivotMeta.className, where, {
|
|
783
|
+
ctx,
|
|
784
|
+
...options,
|
|
785
|
+
fields,
|
|
786
|
+
exclude: childExclude,
|
|
787
|
+
orderBy: this.getPivotOrderBy(prop, pivotProp1, orderBy, options?.orderBy),
|
|
788
|
+
populate: [{ field: populateField, strategy: core_1.LoadStrategy.JOINED, joinType: query_1.JoinType.innerJoin, children: populate }],
|
|
789
|
+
populateWhere: undefined,
|
|
790
|
+
// @ts-ignore
|
|
791
|
+
_populateWhere: 'infer',
|
|
792
|
+
populateFilter: !core_1.Utils.isEmpty(options?.populateFilter) ? { [pivotProp2.name]: options?.populateFilter } : undefined,
|
|
830
793
|
});
|
|
831
|
-
qb.clearRawFragmentsCache();
|
|
832
794
|
const map = {};
|
|
833
|
-
const pkProps = ownerMeta.getPrimaryProps();
|
|
834
795
|
for (const owner of owners) {
|
|
835
|
-
const key = core_1.Utils.getPrimaryKeyHash(
|
|
836
|
-
const pkProp = pkProps[idx];
|
|
837
|
-
return pkProp.customType ? pkProp.customType.convertToJSValue(owner[idx], this.platform) : owner[idx];
|
|
838
|
-
}));
|
|
796
|
+
const key = core_1.Utils.getPrimaryKeyHash(owner);
|
|
839
797
|
map[key] = [];
|
|
840
798
|
}
|
|
841
|
-
for (const item of
|
|
842
|
-
const key = core_1.Utils.getPrimaryKeyHash(
|
|
843
|
-
|
|
844
|
-
return pkProp.customType ? pkProp.customType.convertToJSValue(item[`fk__${col}`], this.platform) : item[`fk__${col}`];
|
|
845
|
-
}));
|
|
846
|
-
map[key].push(item);
|
|
847
|
-
prop.joinColumns.forEach(col => delete item[`fk__${col}`]);
|
|
848
|
-
prop.inverseJoinColumns.forEach((col, idx) => {
|
|
849
|
-
core_1.Utils.renameKey(item, `fk__${col}`, prop.targetMeta.primaryKeys[idx]);
|
|
850
|
-
});
|
|
799
|
+
for (const item of res) {
|
|
800
|
+
const key = core_1.Utils.getPrimaryKeyHash(core_1.Utils.asArray(item[pivotProp2.name]));
|
|
801
|
+
map[key].push(item[pivotProp1.name]);
|
|
851
802
|
}
|
|
852
803
|
return map;
|
|
853
804
|
}
|
|
854
|
-
getPivotOrderBy(prop, pivotProp,
|
|
855
|
-
// FIXME this is ignoring the rest of the array items
|
|
805
|
+
getPivotOrderBy(prop, pivotProp, orderBy, parentOrderBy) {
|
|
856
806
|
if (!core_1.Utils.isEmpty(orderBy)) {
|
|
857
|
-
return
|
|
807
|
+
return core_1.Utils.asArray(orderBy).map(o => ({ [pivotProp.name]: o }));
|
|
808
|
+
}
|
|
809
|
+
if (prop.kind === core_1.ReferenceKind.MANY_TO_MANY && core_1.Utils.asArray(parentOrderBy).some(o => o[prop.name])) {
|
|
810
|
+
return core_1.Utils.asArray(parentOrderBy)
|
|
811
|
+
.filter(o => o[prop.name])
|
|
812
|
+
.map(o => ({ [pivotProp.name]: o[prop.name] }));
|
|
858
813
|
}
|
|
859
814
|
if (!core_1.Utils.isEmpty(prop.orderBy)) {
|
|
860
|
-
return
|
|
815
|
+
return core_1.Utils.asArray(prop.orderBy).map(o => ({ [pivotProp.name]: o }));
|
|
861
816
|
}
|
|
862
817
|
if (prop.fixedOrder) {
|
|
863
|
-
return [{ [
|
|
818
|
+
return [{ [prop.fixedOrderColumn]: core_1.QueryOrder.ASC }];
|
|
864
819
|
}
|
|
865
820
|
return [];
|
|
866
821
|
}
|
|
@@ -878,7 +833,7 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
878
833
|
const toPopulate = meta.relations
|
|
879
834
|
.filter(prop => prop.kind === core_1.ReferenceKind.ONE_TO_ONE && !prop.owner && !prop.lazy && !relationsToPopulate.includes(prop.name))
|
|
880
835
|
.filter(prop => fields.length === 0 || fields.some(f => prop.name === f || prop.name.startsWith(`${String(f)}.`)))
|
|
881
|
-
.map(prop => ({ field: `${prop.name}:ref`, strategy:
|
|
836
|
+
.map(prop => ({ field: `${prop.name}:ref`, strategy: core_1.LoadStrategy.JOINED }));
|
|
882
837
|
return [...populate, ...toPopulate];
|
|
883
838
|
}
|
|
884
839
|
/**
|
|
@@ -886,16 +841,17 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
886
841
|
*/
|
|
887
842
|
joinedProps(meta, populate, options) {
|
|
888
843
|
return populate.filter(hint => {
|
|
889
|
-
const [propName
|
|
844
|
+
const [propName] = hint.field.split(':', 2);
|
|
890
845
|
const prop = meta.properties[propName] || {};
|
|
891
|
-
|
|
846
|
+
const strategy = (0, core_1.getLoadingStrategy)(hint.strategy || prop.strategy || options?.strategy || this.config.get('loadStrategy'), prop.kind);
|
|
847
|
+
if (hint.filter && [core_1.ReferenceKind.ONE_TO_ONE, core_1.ReferenceKind.MANY_TO_ONE].includes(prop.kind) && !prop.nullable) {
|
|
892
848
|
return true;
|
|
893
849
|
}
|
|
894
850
|
// skip redundant joins for 1:1 owner population hints when using `mapToPk`
|
|
895
851
|
if (prop.kind === core_1.ReferenceKind.ONE_TO_ONE && prop.mapToPk && prop.owner) {
|
|
896
852
|
return false;
|
|
897
853
|
}
|
|
898
|
-
if (
|
|
854
|
+
if (strategy !== core_1.LoadStrategy.JOINED) {
|
|
899
855
|
// force joined strategy for explicit 1:1 owner populate hint as it would require a join anyway
|
|
900
856
|
return prop.kind === core_1.ReferenceKind.ONE_TO_ONE && !prop.owner;
|
|
901
857
|
}
|
|
@@ -906,31 +862,26 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
906
862
|
* @internal
|
|
907
863
|
*/
|
|
908
864
|
mergeJoinedResult(rawResults, meta, joinedProps) {
|
|
865
|
+
if (rawResults.length <= 1) {
|
|
866
|
+
return rawResults;
|
|
867
|
+
}
|
|
909
868
|
const res = [];
|
|
910
869
|
const map = {};
|
|
870
|
+
const collectionsToMerge = {};
|
|
871
|
+
const hints = joinedProps.map(hint => {
|
|
872
|
+
const [propName, ref] = hint.field.split(':', 2);
|
|
873
|
+
return { propName, ref, children: hint.children };
|
|
874
|
+
});
|
|
911
875
|
for (const item of rawResults) {
|
|
912
876
|
const pk = core_1.Utils.getCompositeKeyHash(item, meta);
|
|
913
877
|
if (map[pk]) {
|
|
914
|
-
for (const
|
|
915
|
-
const [propName, ref] = hint.field.split(':', 2);
|
|
916
|
-
const prop = meta.properties[propName];
|
|
878
|
+
for (const { propName } of hints) {
|
|
917
879
|
if (!item[propName]) {
|
|
918
880
|
continue;
|
|
919
881
|
}
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
}
|
|
924
|
-
switch (prop.kind) {
|
|
925
|
-
case core_1.ReferenceKind.ONE_TO_MANY:
|
|
926
|
-
case core_1.ReferenceKind.MANY_TO_MANY:
|
|
927
|
-
map[pk][propName] = this.mergeJoinedResult([...map[pk][propName], ...item[propName]], prop.targetMeta, hint.children ?? []);
|
|
928
|
-
break;
|
|
929
|
-
case core_1.ReferenceKind.MANY_TO_ONE:
|
|
930
|
-
case core_1.ReferenceKind.ONE_TO_ONE:
|
|
931
|
-
map[pk][propName] = this.mergeJoinedResult([map[pk][propName], item[propName]], prop.targetMeta, hint.children ?? [])[0];
|
|
932
|
-
break;
|
|
933
|
-
}
|
|
882
|
+
collectionsToMerge[pk] ??= {};
|
|
883
|
+
collectionsToMerge[pk][propName] ??= [map[pk][propName]];
|
|
884
|
+
collectionsToMerge[pk][propName].push(item[propName]);
|
|
934
885
|
}
|
|
935
886
|
}
|
|
936
887
|
else {
|
|
@@ -938,6 +889,31 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
938
889
|
res.push(item);
|
|
939
890
|
}
|
|
940
891
|
}
|
|
892
|
+
for (const pk in collectionsToMerge) {
|
|
893
|
+
const entity = map[pk];
|
|
894
|
+
const collections = collectionsToMerge[pk];
|
|
895
|
+
for (const { propName, ref, children } of hints) {
|
|
896
|
+
if (!collections[propName]) {
|
|
897
|
+
continue;
|
|
898
|
+
}
|
|
899
|
+
const prop = meta.properties[propName];
|
|
900
|
+
const items = collections[propName].flat();
|
|
901
|
+
if ([core_1.ReferenceKind.ONE_TO_MANY, core_1.ReferenceKind.MANY_TO_MANY].includes(prop.kind) && ref) {
|
|
902
|
+
entity[propName] = items;
|
|
903
|
+
continue;
|
|
904
|
+
}
|
|
905
|
+
switch (prop.kind) {
|
|
906
|
+
case core_1.ReferenceKind.ONE_TO_MANY:
|
|
907
|
+
case core_1.ReferenceKind.MANY_TO_MANY:
|
|
908
|
+
entity[propName] = this.mergeJoinedResult(items, prop.targetMeta, children ?? []);
|
|
909
|
+
break;
|
|
910
|
+
case core_1.ReferenceKind.MANY_TO_ONE:
|
|
911
|
+
case core_1.ReferenceKind.ONE_TO_ONE:
|
|
912
|
+
entity[propName] = this.mergeJoinedResult(items, prop.targetMeta, children ?? [])[0];
|
|
913
|
+
break;
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
}
|
|
941
917
|
return res;
|
|
942
918
|
}
|
|
943
919
|
getFieldsForJoinedLoad(qb, meta, explicitFields, exclude, populate = [], options, parentTableAlias, parentJoinPath, count) {
|
|
@@ -964,7 +940,7 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
964
940
|
const [propName, ref] = hint.field.split(':', 2);
|
|
965
941
|
const prop = meta.properties[propName];
|
|
966
942
|
// ignore ref joins of known FKs unless it's a filter hint
|
|
967
|
-
if (ref && !hint.filter && (prop.kind === core_1.ReferenceKind.MANY_TO_ONE || (prop.kind === core_1.ReferenceKind.ONE_TO_ONE &&
|
|
943
|
+
if (ref && !hint.filter && (prop.kind === core_1.ReferenceKind.MANY_TO_ONE || (prop.kind === core_1.ReferenceKind.ONE_TO_ONE && prop.owner))) {
|
|
968
944
|
continue;
|
|
969
945
|
}
|
|
970
946
|
if (count && (!options?.populateFilter || !options.populateFilter[prop.name])) {
|
|
@@ -978,11 +954,14 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
978
954
|
if (!parentJoinPath && populateWhereAll && !hint.filter && !path.startsWith('[populate]')) {
|
|
979
955
|
path = '[populate]' + path;
|
|
980
956
|
}
|
|
957
|
+
const mandatoryToOneProperty = [core_1.ReferenceKind.MANY_TO_ONE, core_1.ReferenceKind.ONE_TO_ONE].includes(prop.kind) && !prop.nullable;
|
|
981
958
|
const joinType = pivotRefJoin
|
|
982
959
|
? query_1.JoinType.pivotJoin
|
|
983
|
-
: hint.
|
|
984
|
-
?
|
|
985
|
-
:
|
|
960
|
+
: hint.joinType
|
|
961
|
+
? hint.joinType
|
|
962
|
+
: (hint.filter && !prop.nullable) || mandatoryToOneProperty
|
|
963
|
+
? query_1.JoinType.innerJoin
|
|
964
|
+
: query_1.JoinType.leftJoin;
|
|
986
965
|
qb.join(field, tableAlias, {}, joinType, path);
|
|
987
966
|
if (pivotRefJoin) {
|
|
988
967
|
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}`)));
|
|
@@ -1000,7 +979,7 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
1000
979
|
if (!ref && !prop.mapToPk) {
|
|
1001
980
|
fields.push(...this.getFieldsForJoinedLoad(qb, meta2, childExplicitFields.length === 0 ? undefined : childExplicitFields, childExclude, hint.children, options, tableAlias, path, count));
|
|
1002
981
|
}
|
|
1003
|
-
else if (hint.filter || prop.mapToPk) {
|
|
982
|
+
else if (hint.filter || prop.mapToPk || (ref && [core_1.ReferenceKind.MANY_TO_ONE, core_1.ReferenceKind.ONE_TO_ONE].includes(prop.kind))) {
|
|
1004
983
|
fields.push(...prop.referencedColumnNames.map(col => qb.helper.mapper(`${tableAlias}.${col}`, qb.type, undefined, `${tableAlias}__${col}`)));
|
|
1005
984
|
}
|
|
1006
985
|
}
|
|
@@ -1078,7 +1057,7 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
1078
1057
|
for (const prop of meta.relations) {
|
|
1079
1058
|
if (collections[prop.name]) {
|
|
1080
1059
|
const pivotMeta = this.metadata.find(prop.pivotEntity);
|
|
1081
|
-
const persister = new PivotCollectionPersister_1.PivotCollectionPersister(pivotMeta, this, options?.ctx, options?.schema);
|
|
1060
|
+
const persister = new PivotCollectionPersister_1.PivotCollectionPersister(pivotMeta, this, options?.ctx, options?.schema, options?.loggerContext);
|
|
1082
1061
|
persister.enqueueUpdate(prop, collections[prop.name], clear, pks);
|
|
1083
1062
|
await this.rethrow(persister.execute());
|
|
1084
1063
|
}
|
|
@@ -1145,7 +1124,7 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
1145
1124
|
let path = parentPath;
|
|
1146
1125
|
const meta2 = this.metadata.find(prop.type);
|
|
1147
1126
|
const childOrder = orderHint[prop.name];
|
|
1148
|
-
if (![core_1.ReferenceKind.MANY_TO_ONE, core_1.ReferenceKind.ONE_TO_ONE].includes(prop.kind) || !prop.owner || core_1.Utils.isPlainObject(childOrder)) {
|
|
1127
|
+
if (prop.kind !== core_1.ReferenceKind.SCALAR && (![core_1.ReferenceKind.MANY_TO_ONE, core_1.ReferenceKind.ONE_TO_ONE].includes(prop.kind) || !prop.owner || core_1.Utils.isPlainObject(childOrder))) {
|
|
1149
1128
|
path += `.${propName}`;
|
|
1150
1129
|
}
|
|
1151
1130
|
if (prop.kind === core_1.ReferenceKind.MANY_TO_MANY && typeof childOrder !== 'object') {
|
|
@@ -1153,10 +1132,10 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
1153
1132
|
}
|
|
1154
1133
|
const join = qb.getJoinForPath(path, { matchPopulateJoins: true });
|
|
1155
1134
|
const propAlias = qb.getAliasForJoinPath(join ?? path, { matchPopulateJoins: true }) ?? parentAlias;
|
|
1156
|
-
if (!join
|
|
1135
|
+
if (!join) {
|
|
1157
1136
|
continue;
|
|
1158
1137
|
}
|
|
1159
|
-
if (![core_1.ReferenceKind.SCALAR, core_1.ReferenceKind.EMBEDDED].includes(prop.kind) && typeof childOrder === 'object') {
|
|
1138
|
+
if (join && ![core_1.ReferenceKind.SCALAR, core_1.ReferenceKind.EMBEDDED].includes(prop.kind) && typeof childOrder === 'object') {
|
|
1160
1139
|
const children = this.buildPopulateOrderBy(qb, meta2, core_1.Utils.asArray(childOrder), path, explicit, propAlias);
|
|
1161
1140
|
orderBy.push(...children);
|
|
1162
1141
|
continue;
|
|
@@ -1311,16 +1290,19 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
1311
1290
|
ret.push('*');
|
|
1312
1291
|
}
|
|
1313
1292
|
if (ret.length > 0 && !hasExplicitFields && addFormulas) {
|
|
1314
|
-
meta.props
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1293
|
+
for (const prop of meta.props) {
|
|
1294
|
+
if (lazyProps.includes(prop)) {
|
|
1295
|
+
continue;
|
|
1296
|
+
}
|
|
1297
|
+
if (prop.formula) {
|
|
1298
|
+
const a = this.platform.quoteIdentifier(alias);
|
|
1299
|
+
const aliased = this.platform.quoteIdentifier(prop.fieldNames[0]);
|
|
1300
|
+
ret.push((0, core_1.raw)(`${prop.formula(a)} as ${aliased}`));
|
|
1301
|
+
}
|
|
1302
|
+
if (!prop.object && (prop.hasConvertToDatabaseValueSQL || prop.hasConvertToJSValueSQL)) {
|
|
1303
|
+
ret.push(prop.name);
|
|
1304
|
+
}
|
|
1305
|
+
}
|
|
1324
1306
|
}
|
|
1325
1307
|
// add joined relations after the root entity fields
|
|
1326
1308
|
if (joinedProps.length > 0) {
|
package/AbstractSqlPlatform.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Platform, type Constructor, type EntityManager, type EntityRepository, type IDatabaseDriver, type MikroORM } from '@mikro-orm/core';
|
|
1
|
+
import { Platform, type Constructor, type EntityManager, type EntityRepository, type IDatabaseDriver, type MikroORM, type IsolationLevel } from '@mikro-orm/core';
|
|
2
2
|
import { SqlSchemaGenerator, type SchemaHelper } from './schema';
|
|
3
3
|
import type { IndexDef } from './typings';
|
|
4
4
|
export declare abstract class AbstractSqlPlatform extends Platform {
|
|
@@ -10,6 +10,15 @@ export declare abstract class AbstractSqlPlatform extends Platform {
|
|
|
10
10
|
/** @inheritDoc */
|
|
11
11
|
lookupExtensions(orm: MikroORM): void;
|
|
12
12
|
getSchemaGenerator(driver: IDatabaseDriver, em?: EntityManager): SqlSchemaGenerator;
|
|
13
|
+
getBeginTransactionSQL(options?: {
|
|
14
|
+
isolationLevel?: IsolationLevel;
|
|
15
|
+
readOnly?: boolean;
|
|
16
|
+
}): string[];
|
|
17
|
+
getCommitTransactionSQL(): string;
|
|
18
|
+
getRollbackTransactionSQL(): string;
|
|
19
|
+
getSavepointSQL(savepointName: string): string;
|
|
20
|
+
getRollbackToSavepointSQL(savepointName: string): string;
|
|
21
|
+
getReleaseSavepointSQL(savepointName: string): string;
|
|
13
22
|
quoteValue(value: any): string;
|
|
14
23
|
escape(value: any): string;
|
|
15
24
|
getSearchJsonPropertySQL(path: string, type: string, aliased: boolean): string;
|
package/AbstractSqlPlatform.js
CHANGED
|
@@ -27,6 +27,27 @@ class AbstractSqlPlatform extends core_1.Platform {
|
|
|
27
27
|
getSchemaGenerator(driver, em) {
|
|
28
28
|
return new schema_1.SqlSchemaGenerator(em ?? driver);
|
|
29
29
|
}
|
|
30
|
+
getBeginTransactionSQL(options) {
|
|
31
|
+
if (options?.isolationLevel) {
|
|
32
|
+
return [`set transaction isolation level ${options.isolationLevel}`, 'begin'];
|
|
33
|
+
}
|
|
34
|
+
return ['begin'];
|
|
35
|
+
}
|
|
36
|
+
getCommitTransactionSQL() {
|
|
37
|
+
return 'commit';
|
|
38
|
+
}
|
|
39
|
+
getRollbackTransactionSQL() {
|
|
40
|
+
return 'rollback';
|
|
41
|
+
}
|
|
42
|
+
getSavepointSQL(savepointName) {
|
|
43
|
+
return `savepoint ${this.quoteIdentifier(savepointName)}`;
|
|
44
|
+
}
|
|
45
|
+
getRollbackToSavepointSQL(savepointName) {
|
|
46
|
+
return `rollback to savepoint ${this.quoteIdentifier(savepointName)}`;
|
|
47
|
+
}
|
|
48
|
+
getReleaseSavepointSQL(savepointName) {
|
|
49
|
+
return `release savepoint ${this.quoteIdentifier(savepointName)}`;
|
|
50
|
+
}
|
|
30
51
|
quoteValue(value) {
|
|
31
52
|
if (core_1.Utils.isRawSql(value)) {
|
|
32
53
|
return this.formatQuery(value.sql, value.params ?? []);
|
|
@@ -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';
|
|
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;
|
|
@@ -41,16 +41,18 @@ class PivotCollectionPersister {
|
|
|
41
41
|
driver;
|
|
42
42
|
ctx;
|
|
43
43
|
schema;
|
|
44
|
+
loggerContext;
|
|
44
45
|
platform;
|
|
45
46
|
inserts = new Map();
|
|
46
47
|
deletes = new Map();
|
|
47
48
|
batchSize;
|
|
48
49
|
order = 0;
|
|
49
|
-
constructor(meta, driver, ctx, schema) {
|
|
50
|
+
constructor(meta, driver, ctx, schema, loggerContext) {
|
|
50
51
|
this.meta = meta;
|
|
51
52
|
this.driver = driver;
|
|
52
53
|
this.ctx = ctx;
|
|
53
54
|
this.schema = schema;
|
|
55
|
+
this.loggerContext = loggerContext;
|
|
54
56
|
this.platform = this.driver.getPlatform();
|
|
55
57
|
this.batchSize = this.driver.config.get('batchSize');
|
|
56
58
|
}
|
|
@@ -102,6 +104,7 @@ class PivotCollectionPersister {
|
|
|
102
104
|
await this.driver.nativeDelete(this.meta.className, cond, {
|
|
103
105
|
ctx: this.ctx,
|
|
104
106
|
schema: this.schema,
|
|
107
|
+
loggerContext: this.loggerContext,
|
|
105
108
|
});
|
|
106
109
|
}
|
|
107
110
|
}
|
|
@@ -122,12 +125,13 @@ class PivotCollectionPersister {
|
|
|
122
125
|
schema: this.schema,
|
|
123
126
|
convertCustomTypes: false,
|
|
124
127
|
processCollections: false,
|
|
128
|
+
loggerContext: this.loggerContext,
|
|
125
129
|
});
|
|
126
130
|
}
|
|
127
131
|
}
|
|
128
132
|
else {
|
|
129
133
|
await core_1.Utils.runSerial(items, item => {
|
|
130
|
-
return this.driver.createQueryBuilder(this.meta.className, this.ctx, 'write')
|
|
134
|
+
return this.driver.createQueryBuilder(this.meta.className, this.ctx, 'write', false, this.loggerContext)
|
|
131
135
|
.withSchema(this.schema)
|
|
132
136
|
.insert(item)
|
|
133
137
|
.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)
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type SimpleColumnMeta, type Type, type TransformContext } from '@mikro-orm/core';
|
|
1
|
+
import { type SimpleColumnMeta, type Type, type TransformContext, type IsolationLevel } from '@mikro-orm/core';
|
|
2
2
|
import { MySqlSchemaHelper } from './MySqlSchemaHelper';
|
|
3
3
|
import { MySqlExceptionConverter } from './MySqlExceptionConverter';
|
|
4
4
|
import { AbstractSqlPlatform } from '../../AbstractSqlPlatform';
|
|
@@ -13,6 +13,10 @@ export declare class MySqlPlatform extends AbstractSqlPlatform {
|
|
|
13
13
|
readonly "desc nulls last": "is null";
|
|
14
14
|
};
|
|
15
15
|
getDefaultCharset(): string;
|
|
16
|
+
getBeginTransactionSQL(options?: {
|
|
17
|
+
isolationLevel?: IsolationLevel;
|
|
18
|
+
readOnly?: boolean;
|
|
19
|
+
}): string[];
|
|
16
20
|
convertJsonToDatabaseValue(value: unknown, context?: TransformContext): unknown;
|
|
17
21
|
getJsonIndexDefinition(index: IndexDef): string[];
|
|
18
22
|
getBooleanTypeDeclarationSQL(): string;
|
|
@@ -17,6 +17,20 @@ class MySqlPlatform extends AbstractSqlPlatform_1.AbstractSqlPlatform {
|
|
|
17
17
|
getDefaultCharset() {
|
|
18
18
|
return 'utf8mb4';
|
|
19
19
|
}
|
|
20
|
+
getBeginTransactionSQL(options) {
|
|
21
|
+
if (options?.isolationLevel || options?.readOnly) {
|
|
22
|
+
const parts = [];
|
|
23
|
+
if (options.isolationLevel) {
|
|
24
|
+
parts.push(`isolation level ${options.isolationLevel}`);
|
|
25
|
+
}
|
|
26
|
+
if (options.readOnly) {
|
|
27
|
+
parts.push('read only');
|
|
28
|
+
}
|
|
29
|
+
const sql = `set transaction ${parts.join(', ')}`;
|
|
30
|
+
return [sql, 'begin'];
|
|
31
|
+
}
|
|
32
|
+
return ['begin'];
|
|
33
|
+
}
|
|
20
34
|
convertJsonToDatabaseValue(value, context) {
|
|
21
35
|
if (context?.mode === 'query') {
|
|
22
36
|
return value;
|
|
@@ -204,7 +204,14 @@ class MySqlSchemaHelper extends SchemaHelper_1.SchemaHelper {
|
|
|
204
204
|
col.defaultTo(null);
|
|
205
205
|
}
|
|
206
206
|
else {
|
|
207
|
-
|
|
207
|
+
const columnType = column.type.toLowerCase();
|
|
208
|
+
// https://dev.mysql.com/doc/refman/9.0/en/data-type-defaults.html
|
|
209
|
+
const needsExpression = ['blob', 'text', 'json', 'point', 'linestring', 'polygon', 'multipoint', 'multilinestring', 'multipolygon', 'geometrycollection'].some(type => columnType.startsWith(type));
|
|
210
|
+
let defaultSql = needsExpression && !column.default.startsWith('(') ? `(${column.default})` : column.default;
|
|
211
|
+
if (column.extra) {
|
|
212
|
+
defaultSql += ' ' + column.extra;
|
|
213
|
+
}
|
|
214
|
+
col.defaultTo(knex.raw(defaultSql));
|
|
208
215
|
}
|
|
209
216
|
}
|
|
210
217
|
return col;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type EntityProperty } from '@mikro-orm/core';
|
|
1
|
+
import { type EntityProperty, type IsolationLevel } from '@mikro-orm/core';
|
|
2
2
|
import { AbstractSqlPlatform } from '../../AbstractSqlPlatform';
|
|
3
3
|
export declare abstract class BaseSqlitePlatform extends AbstractSqlPlatform {
|
|
4
4
|
usesDefaultKeyword(): boolean;
|
|
@@ -7,6 +7,10 @@ export declare abstract class BaseSqlitePlatform extends AbstractSqlPlatform {
|
|
|
7
7
|
getDateTimeTypeDeclarationSQL(column: {
|
|
8
8
|
length: number;
|
|
9
9
|
}): string;
|
|
10
|
+
getBeginTransactionSQL(options?: {
|
|
11
|
+
isolationLevel?: IsolationLevel;
|
|
12
|
+
readOnly?: boolean;
|
|
13
|
+
}): string[];
|
|
10
14
|
getEnumTypeDeclarationSQL(column: {
|
|
11
15
|
items?: unknown[];
|
|
12
16
|
fieldNames: string[];
|
|
@@ -16,6 +16,9 @@ class BaseSqlitePlatform extends AbstractSqlPlatform_1.AbstractSqlPlatform {
|
|
|
16
16
|
getDateTimeTypeDeclarationSQL(column) {
|
|
17
17
|
return 'datetime';
|
|
18
18
|
}
|
|
19
|
+
getBeginTransactionSQL(options) {
|
|
20
|
+
return ['begin'];
|
|
21
|
+
}
|
|
19
22
|
getEnumTypeDeclarationSQL(column) {
|
|
20
23
|
if (column.items?.every(item => core_1.Utils.isString(item))) {
|
|
21
24
|
return 'text';
|
package/index.mjs
CHANGED
|
@@ -219,12 +219,16 @@ export const compareBuffers = mod.compareBuffers;
|
|
|
219
219
|
export const compareObjects = mod.compareObjects;
|
|
220
220
|
export const createSqlFunction = mod.createSqlFunction;
|
|
221
221
|
export const defineConfig = mod.defineConfig;
|
|
222
|
+
export const defineEntity = mod.defineEntity;
|
|
222
223
|
export const equals = mod.equals;
|
|
224
|
+
export const expandDotPaths = mod.expandDotPaths;
|
|
225
|
+
export const getLoadingStrategy = mod.getLoadingStrategy;
|
|
223
226
|
export const getOnConflictFields = mod.getOnConflictFields;
|
|
224
227
|
export const getOnConflictReturningFields = mod.getOnConflictReturningFields;
|
|
225
228
|
export const helper = mod.helper;
|
|
226
229
|
export const knex = mod.knex;
|
|
227
230
|
export const parseJsonSafe = mod.parseJsonSafe;
|
|
231
|
+
export const quote = mod.quote;
|
|
228
232
|
export const raw = mod.raw;
|
|
229
233
|
export const ref = mod.ref;
|
|
230
234
|
export const rel = mod.rel;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mikro-orm/knex",
|
|
3
|
-
"version": "6.4.17-dev.
|
|
3
|
+
"version": "6.4.17-dev.91",
|
|
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
|
"main": "index.js",
|
|
6
6
|
"module": "index.mjs",
|
|
@@ -58,7 +58,7 @@
|
|
|
58
58
|
"access": "public"
|
|
59
59
|
},
|
|
60
60
|
"dependencies": {
|
|
61
|
-
"fs-extra": "11.3.
|
|
61
|
+
"fs-extra": "11.3.1",
|
|
62
62
|
"knex": "3.1.0",
|
|
63
63
|
"sqlstring": "2.3.3"
|
|
64
64
|
},
|
|
@@ -66,7 +66,7 @@
|
|
|
66
66
|
"@mikro-orm/core": "^6.4.16"
|
|
67
67
|
},
|
|
68
68
|
"peerDependencies": {
|
|
69
|
-
"@mikro-orm/core": "6.4.17-dev.
|
|
69
|
+
"@mikro-orm/core": "6.4.17-dev.91",
|
|
70
70
|
"better-sqlite3": "*",
|
|
71
71
|
"libsql": "*",
|
|
72
72
|
"mariadb": "*"
|
package/query/CriteriaNode.js
CHANGED
|
@@ -69,7 +69,7 @@ class CriteriaNode {
|
|
|
69
69
|
}
|
|
70
70
|
}
|
|
71
71
|
renameFieldToPK(qb) {
|
|
72
|
-
let joinAlias = qb.getAliasForJoinPath(this.getPath());
|
|
72
|
+
let joinAlias = qb.getAliasForJoinPath(this.getPath(), { matchPopulateJoins: true });
|
|
73
73
|
if (!joinAlias && this.parent && [core_1.ReferenceKind.MANY_TO_ONE, core_1.ReferenceKind.ONE_TO_ONE].includes(this.prop.kind) && this.prop.owner) {
|
|
74
74
|
joinAlias = qb.getAliasForJoinPath(this.parent.getPath());
|
|
75
75
|
return core_1.Utils.getPrimaryKeyHash(this.prop.joinColumns.map(col => `${joinAlias ?? qb.alias}.${col}`));
|
|
@@ -49,13 +49,14 @@ class CriteriaNodeFactory {
|
|
|
49
49
|
static createObjectItemNode(metadata, entityName, node, payload, key, meta) {
|
|
50
50
|
const prop = meta?.properties[key];
|
|
51
51
|
const childEntity = prop && prop.kind !== core_1.ReferenceKind.SCALAR ? prop.type : entityName;
|
|
52
|
-
|
|
52
|
+
const isNotEmbedded = prop?.kind !== core_1.ReferenceKind.EMBEDDED;
|
|
53
|
+
if (isNotEmbedded && prop?.customType instanceof core_1.JsonType) {
|
|
53
54
|
return this.createScalarNode(metadata, childEntity, payload[key], node, key);
|
|
54
55
|
}
|
|
55
56
|
if (prop?.kind === core_1.ReferenceKind.SCALAR && payload[key] != null && Object.keys(payload[key]).some(f => core_1.Utils.isGroupOperator(f))) {
|
|
56
57
|
throw core_1.ValidationError.cannotUseGroupOperatorsInsideScalars(entityName, prop.name, payload);
|
|
57
58
|
}
|
|
58
|
-
if (
|
|
59
|
+
if (isNotEmbedded) {
|
|
59
60
|
return this.createNode(metadata, childEntity, payload[key], node, key);
|
|
60
61
|
}
|
|
61
62
|
if (payload[key] == null) {
|
|
@@ -72,11 +73,12 @@ class CriteriaNodeFactory {
|
|
|
72
73
|
throw core_1.ValidationError.cannotUseOperatorsInsideEmbeddables(entityName, prop.name, payload);
|
|
73
74
|
}
|
|
74
75
|
const map = Object.keys(payload[key]).reduce((oo, k) => {
|
|
75
|
-
|
|
76
|
+
const embeddedProp = prop.embeddedProps[k] ?? Object.values(prop.embeddedProps).find(p => p.name === k);
|
|
77
|
+
if (!embeddedProp && !allowedOperators.includes(k)) {
|
|
76
78
|
throw core_1.ValidationError.invalidEmbeddableQuery(entityName, k, prop.type);
|
|
77
79
|
}
|
|
78
|
-
if (
|
|
79
|
-
oo[
|
|
80
|
+
if (embeddedProp) {
|
|
81
|
+
oo[embeddedProp.name] = payload[key][k];
|
|
80
82
|
}
|
|
81
83
|
else if (typeof payload[key][k] === 'object') {
|
|
82
84
|
oo[k] = JSON.stringify(payload[key][k]);
|
|
@@ -9,7 +9,8 @@ const enums_1 = require("./enums");
|
|
|
9
9
|
*/
|
|
10
10
|
class ObjectCriteriaNode extends CriteriaNode_1.CriteriaNode {
|
|
11
11
|
process(qb, options) {
|
|
12
|
-
const
|
|
12
|
+
const matchPopulateJoins = options?.matchPopulateJoins || (this.prop && [core_1.ReferenceKind.MANY_TO_ONE, core_1.ReferenceKind.ONE_TO_ONE].includes(this.prop.kind));
|
|
13
|
+
const nestedAlias = qb.getAliasForJoinPath(this.getPath(), { ...options, matchPopulateJoins });
|
|
13
14
|
const ownerAlias = options?.alias || qb.alias;
|
|
14
15
|
const keys = Object.keys(this.payload);
|
|
15
16
|
let alias = options?.alias;
|
|
@@ -209,7 +210,23 @@ class ObjectCriteriaNode extends CriteriaNode_1.CriteriaNode {
|
|
|
209
210
|
}
|
|
210
211
|
else {
|
|
211
212
|
const prev = qb._fields?.slice();
|
|
212
|
-
|
|
213
|
+
const toOneProperty = [core_1.ReferenceKind.MANY_TO_ONE, core_1.ReferenceKind.ONE_TO_ONE].includes(this.prop.kind);
|
|
214
|
+
const joinType = toOneProperty && !this.prop.nullable
|
|
215
|
+
? enums_1.JoinType.innerJoin
|
|
216
|
+
: enums_1.JoinType.leftJoin;
|
|
217
|
+
qb[method](field, nestedAlias, undefined, joinType, path);
|
|
218
|
+
// if the property is nullable, we need to use left join, so we mimic the inner join behaviour
|
|
219
|
+
// with an exclusive condition on the join columns:
|
|
220
|
+
// - if the owning column is null, the row is missing, we don't apply the filter
|
|
221
|
+
// - if the target column is not null, the row is matched, we apply the filter
|
|
222
|
+
if (toOneProperty && this.prop.nullable && options?.filter) {
|
|
223
|
+
qb.andWhere({
|
|
224
|
+
$or: [
|
|
225
|
+
{ [field]: null },
|
|
226
|
+
{ [nestedAlias + '.' + core_1.Utils.getPrimaryKeyHash(this.prop.referencedPKs)]: { $ne: null } },
|
|
227
|
+
],
|
|
228
|
+
});
|
|
229
|
+
}
|
|
213
230
|
if (!qb.hasFlag(core_1.QueryFlag.INFER_POPULATE)) {
|
|
214
231
|
qb._fields = prev;
|
|
215
232
|
}
|
package/query/QueryBuilder.d.ts
CHANGED
|
@@ -299,6 +299,11 @@ export declare class QueryBuilder<Entity extends object = AnyEntity, RootAlias e
|
|
|
299
299
|
processPopulateHint(): void;
|
|
300
300
|
private processPopulateWhere;
|
|
301
301
|
private mergeOnConditions;
|
|
302
|
+
/**
|
|
303
|
+
* When adding an inner join on a left joined relation, we need to nest them,
|
|
304
|
+
* otherwise the inner join could discard rows of the root table.
|
|
305
|
+
*/
|
|
306
|
+
private processNestedJoins;
|
|
302
307
|
private hasToManyJoins;
|
|
303
308
|
protected wrapPaginateSubQuery(meta: EntityMetadata): void;
|
|
304
309
|
private wrapModifySubQuery;
|
package/query/QueryBuilder.js
CHANGED
|
@@ -189,10 +189,10 @@ class QueryBuilder {
|
|
|
189
189
|
subquery = field[1] instanceof QueryBuilder ? field[1].getFormattedQuery() : field[1].toString();
|
|
190
190
|
field = field[0];
|
|
191
191
|
}
|
|
192
|
-
const prop = this.joinReference(field, alias, cond, type, path, schema, subquery);
|
|
192
|
+
const { prop, key } = this.joinReference(field, alias, cond, type, path, schema, subquery);
|
|
193
193
|
const [fromAlias] = this.helper.splitField(field);
|
|
194
194
|
if (subquery) {
|
|
195
|
-
this._joins[
|
|
195
|
+
this._joins[key].subquery = subquery;
|
|
196
196
|
}
|
|
197
197
|
const populate = this._joinedProps.get(fromAlias);
|
|
198
198
|
const item = { field: prop.name, strategy: core_1.LoadStrategy.JOINED, children: [] };
|
|
@@ -927,7 +927,8 @@ class QueryBuilder {
|
|
|
927
927
|
prop.targetMeta = field.mainAlias.metadata;
|
|
928
928
|
field = field.getKnexQuery();
|
|
929
929
|
}
|
|
930
|
-
|
|
930
|
+
const key = `${this.alias}.${prop.name}#${alias}`;
|
|
931
|
+
this._joins[key] = {
|
|
931
932
|
prop,
|
|
932
933
|
alias,
|
|
933
934
|
type,
|
|
@@ -936,7 +937,7 @@ class QueryBuilder {
|
|
|
936
937
|
subquery: field.toString(),
|
|
937
938
|
ownerAlias: this.alias,
|
|
938
939
|
};
|
|
939
|
-
return prop;
|
|
940
|
+
return { prop, key };
|
|
940
941
|
}
|
|
941
942
|
if (!subquery && type.includes('lateral')) {
|
|
942
943
|
throw new Error(`Lateral join can be used only with a sub-query.`);
|
|
@@ -965,6 +966,7 @@ class QueryBuilder {
|
|
|
965
966
|
path ??= `${(Object.values(this._joins).find(j => j.alias === fromAlias)?.path ?? entityName)}.${prop.name}`;
|
|
966
967
|
if (prop.kind === core_1.ReferenceKind.ONE_TO_MANY) {
|
|
967
968
|
this._joins[aliasedName] = this.helper.joinOneToReference(prop, fromAlias, alias, type, cond, schema);
|
|
969
|
+
this._joins[aliasedName].path ??= path;
|
|
968
970
|
}
|
|
969
971
|
else if (prop.kind === core_1.ReferenceKind.MANY_TO_MANY) {
|
|
970
972
|
let pivotAlias = alias;
|
|
@@ -976,17 +978,18 @@ class QueryBuilder {
|
|
|
976
978
|
const joins = this.helper.joinManyToManyReference(prop, fromAlias, alias, pivotAlias, type, cond, path, schema);
|
|
977
979
|
Object.assign(this._joins, joins);
|
|
978
980
|
this.createAlias(prop.pivotEntity, pivotAlias);
|
|
981
|
+
this._joins[aliasedName].path ??= path;
|
|
982
|
+
aliasedName = Object.keys(joins)[1];
|
|
979
983
|
}
|
|
980
984
|
else if (prop.kind === core_1.ReferenceKind.ONE_TO_ONE) {
|
|
981
985
|
this._joins[aliasedName] = this.helper.joinOneToReference(prop, fromAlias, alias, type, cond, schema);
|
|
986
|
+
this._joins[aliasedName].path ??= path;
|
|
982
987
|
}
|
|
983
988
|
else { // MANY_TO_ONE
|
|
984
989
|
this._joins[aliasedName] = this.helper.joinManyToOneReference(prop, fromAlias, alias, type, cond, schema);
|
|
990
|
+
this._joins[aliasedName].path ??= path;
|
|
985
991
|
}
|
|
986
|
-
|
|
987
|
-
this._joins[aliasedName].path = path;
|
|
988
|
-
}
|
|
989
|
-
return prop;
|
|
992
|
+
return { prop, key: aliasedName };
|
|
990
993
|
}
|
|
991
994
|
prepareFields(fields, type = 'where') {
|
|
992
995
|
const ret = [];
|
|
@@ -1161,6 +1164,7 @@ class QueryBuilder {
|
|
|
1161
1164
|
const meta = this.mainAlias.metadata;
|
|
1162
1165
|
this.applyDiscriminatorCondition();
|
|
1163
1166
|
this.processPopulateHint();
|
|
1167
|
+
this.processNestedJoins();
|
|
1164
1168
|
if (meta && (this._fields?.includes('*') || this._fields?.includes(`${this.mainAlias.aliasName}.*`))) {
|
|
1165
1169
|
meta.props
|
|
1166
1170
|
.filter(prop => prop.formula && (!prop.lazy || this.flags.has(core_1.QueryFlag.INCLUDE_LAZY_FORMULAS)))
|
|
@@ -1240,7 +1244,7 @@ class QueryBuilder {
|
|
|
1240
1244
|
if (typeof this[key] === 'object') {
|
|
1241
1245
|
const cond = CriteriaNodeFactory_1.CriteriaNodeFactory
|
|
1242
1246
|
.createNode(this.metadata, this.mainAlias.entityName, this[key])
|
|
1243
|
-
.process(this, { matchPopulateJoins: true, ignoreBranching: true, preferNoBranch: true });
|
|
1247
|
+
.process(this, { matchPopulateJoins: true, ignoreBranching: true, preferNoBranch: true, filter });
|
|
1244
1248
|
// there might be new joins created by processing the `populateWhere` object
|
|
1245
1249
|
joins = Object.values(this._joins);
|
|
1246
1250
|
this.mergeOnConditions(joins, cond, filter);
|
|
@@ -1281,6 +1285,29 @@ class QueryBuilder {
|
|
|
1281
1285
|
}
|
|
1282
1286
|
}
|
|
1283
1287
|
}
|
|
1288
|
+
/**
|
|
1289
|
+
* When adding an inner join on a left joined relation, we need to nest them,
|
|
1290
|
+
* otherwise the inner join could discard rows of the root table.
|
|
1291
|
+
*/
|
|
1292
|
+
processNestedJoins() {
|
|
1293
|
+
if (this.flags.has(core_1.QueryFlag.DISABLE_NESTED_INNER_JOIN)) {
|
|
1294
|
+
return;
|
|
1295
|
+
}
|
|
1296
|
+
const joins = Object.values(this._joins);
|
|
1297
|
+
for (const join of joins) {
|
|
1298
|
+
if (join.type === enums_1.JoinType.innerJoin) {
|
|
1299
|
+
const parentJoin = joins.find(j => j.alias === join.ownerAlias);
|
|
1300
|
+
// https://stackoverflow.com/a/56815807/3665878
|
|
1301
|
+
if (parentJoin?.type === enums_1.JoinType.leftJoin || parentJoin?.type === enums_1.JoinType.nestedLeftJoin) {
|
|
1302
|
+
const nested = (parentJoin.nested ??= new Set());
|
|
1303
|
+
join.type = join.type === enums_1.JoinType.innerJoin
|
|
1304
|
+
? enums_1.JoinType.nestedInnerJoin
|
|
1305
|
+
: enums_1.JoinType.nestedLeftJoin;
|
|
1306
|
+
nested.add(join);
|
|
1307
|
+
}
|
|
1308
|
+
}
|
|
1309
|
+
}
|
|
1310
|
+
}
|
|
1284
1311
|
hasToManyJoins() {
|
|
1285
1312
|
return Object.values(this._joins).some(join => {
|
|
1286
1313
|
return [core_1.ReferenceKind.ONE_TO_MANY, core_1.ReferenceKind.MANY_TO_MANY].includes(join.prop.kind);
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { CriteriaNode } from './CriteriaNode';
|
|
2
|
-
import type {
|
|
2
|
+
import type { ICriteriaNodeProcessOptions, IQueryBuilder } from '../typings';
|
|
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
|
}
|
|
@@ -9,7 +9,9 @@ const enums_1 = require("./enums");
|
|
|
9
9
|
*/
|
|
10
10
|
class ScalarCriteriaNode extends CriteriaNode_1.CriteriaNode {
|
|
11
11
|
process(qb, options) {
|
|
12
|
-
|
|
12
|
+
const matchPopulateJoins = options?.matchPopulateJoins || (this.prop && [core_1.ReferenceKind.MANY_TO_ONE, core_1.ReferenceKind.ONE_TO_ONE].includes(this.prop.kind));
|
|
13
|
+
const nestedAlias = qb.getAliasForJoinPath(this.getPath(), { ...options, matchPopulateJoins });
|
|
14
|
+
if (this.shouldJoin(qb, nestedAlias)) {
|
|
13
15
|
const path = this.getPath();
|
|
14
16
|
const parentPath = this.parent.getPath(); // the parent is always there, otherwise `shouldJoin` would return `false`
|
|
15
17
|
const nestedAlias = qb.getAliasForJoinPath(path) || qb.getNextAlias(this.prop?.pivotTable ?? this.entityName);
|
|
@@ -30,10 +32,10 @@ class ScalarCriteriaNode extends CriteriaNode_1.CriteriaNode {
|
|
|
30
32
|
return this.payload;
|
|
31
33
|
}
|
|
32
34
|
willAutoJoin(qb, alias, options) {
|
|
33
|
-
return this.shouldJoin();
|
|
35
|
+
return this.shouldJoin(qb, alias);
|
|
34
36
|
}
|
|
35
|
-
shouldJoin() {
|
|
36
|
-
if (!this.parent || !this.prop) {
|
|
37
|
+
shouldJoin(qb, nestedAlias) {
|
|
38
|
+
if (!this.parent || !this.prop || (nestedAlias && [enums_1.QueryType.SELECT, enums_1.QueryType.COUNT].includes(qb.type ?? enums_1.QueryType.SELECT))) {
|
|
37
39
|
return false;
|
|
38
40
|
}
|
|
39
41
|
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';
|
|
3
3
|
import type { CheckDef, Column, ForeignKey, IndexDef } from '../typings';
|
|
4
4
|
import type { AbstractSqlPlatform } from '../AbstractSqlPlatform';
|
|
@@ -53,12 +53,13 @@ export declare class DatabaseTable {
|
|
|
53
53
|
private getPropertyTypeForForeignKey;
|
|
54
54
|
private getPropertyTypeForColumn;
|
|
55
55
|
private getPropertyDefaultValue;
|
|
56
|
+
private processIndexExpression;
|
|
56
57
|
addIndex(meta: EntityMetadata, index: {
|
|
57
|
-
properties
|
|
58
|
+
properties?: string | string[];
|
|
58
59
|
name?: string;
|
|
59
60
|
type?: string;
|
|
60
|
-
expression?: string
|
|
61
|
-
deferMode?: DeferMode
|
|
61
|
+
expression?: string | IndexCallback<any>;
|
|
62
|
+
deferMode?: DeferMode | `${DeferMode}`;
|
|
62
63
|
options?: Dictionary;
|
|
63
64
|
}, type: 'index' | 'unique' | 'primary'): void;
|
|
64
65
|
addCheck(check: CheckDef): void;
|
package/schema/DatabaseTable.js
CHANGED
|
@@ -700,6 +700,18 @@ class DatabaseTable {
|
|
|
700
700
|
}
|
|
701
701
|
return '' + val;
|
|
702
702
|
}
|
|
703
|
+
processIndexExpression(expression, meta) {
|
|
704
|
+
if (expression instanceof Function) {
|
|
705
|
+
const exp = expression({ name: this.name, schema: this.schema, toString() {
|
|
706
|
+
if (this.schema) {
|
|
707
|
+
return `${this.schema}.${this.name}`;
|
|
708
|
+
}
|
|
709
|
+
return this.name;
|
|
710
|
+
} }, meta.createColumnMappingObject());
|
|
711
|
+
return exp instanceof core_1.RawQueryFragment ? this.platform.formatQuery(exp.sql, exp.params) : exp;
|
|
712
|
+
}
|
|
713
|
+
return expression;
|
|
714
|
+
}
|
|
703
715
|
addIndex(meta, index, type) {
|
|
704
716
|
const properties = core_1.Utils.unique(core_1.Utils.flatten(core_1.Utils.asArray(index.properties).map(prop => {
|
|
705
717
|
const parts = prop.split('.');
|
|
@@ -741,7 +753,7 @@ class DatabaseTable {
|
|
|
741
753
|
primary: type === 'primary',
|
|
742
754
|
unique: type !== 'index',
|
|
743
755
|
type: index.type,
|
|
744
|
-
expression: index.expression,
|
|
756
|
+
expression: this.processIndexExpression(index.expression, meta),
|
|
745
757
|
options: index.options,
|
|
746
758
|
deferMode: index.deferMode,
|
|
747
759
|
});
|
package/typings.d.ts
CHANGED
|
@@ -75,7 +75,7 @@ export interface IndexDef {
|
|
|
75
75
|
storageEngineIndexType?: 'hash' | 'btree';
|
|
76
76
|
predicate?: Knex.QueryBuilder;
|
|
77
77
|
}>;
|
|
78
|
-
deferMode?: DeferMode
|
|
78
|
+
deferMode?: DeferMode | `${DeferMode}`;
|
|
79
79
|
}
|
|
80
80
|
export interface CheckDef<T = unknown> {
|
|
81
81
|
name: string;
|
|
@@ -172,6 +172,7 @@ export interface ICriteriaNodeProcessOptions {
|
|
|
172
172
|
ignoreBranching?: boolean;
|
|
173
173
|
preferNoBranch?: boolean;
|
|
174
174
|
type?: 'orderBy';
|
|
175
|
+
filter?: boolean;
|
|
175
176
|
}
|
|
176
177
|
export interface ICriteriaNode<T extends object> {
|
|
177
178
|
readonly entityName: string;
|