@mikro-orm/knex 7.0.0-dev.3 → 7.0.0-dev.30
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AbstractSqlConnection.d.ts +6 -4
- package/AbstractSqlConnection.js +33 -19
- package/AbstractSqlDriver.d.ts +7 -7
- package/AbstractSqlDriver.js +169 -163
- package/AbstractSqlPlatform.js +3 -3
- package/PivotCollectionPersister.d.ts +3 -2
- package/PivotCollectionPersister.js +6 -2
- package/README.md +1 -2
- package/dialects/mssql/MsSqlNativeQueryBuilder.d.ts +2 -0
- package/dialects/mssql/MsSqlNativeQueryBuilder.js +48 -5
- package/dialects/mysql/MySqlPlatform.js +2 -1
- package/dialects/sqlite/BaseSqliteConnection.d.ts +1 -1
- package/dialects/sqlite/BaseSqliteConnection.js +8 -2
- package/dialects/sqlite/BaseSqlitePlatform.d.ts +0 -1
- package/dialects/sqlite/BaseSqlitePlatform.js +0 -4
- package/dialects/sqlite/SqliteSchemaHelper.js +1 -1
- package/package.json +4 -4
- package/query/CriteriaNode.d.ts +1 -1
- package/query/CriteriaNode.js +5 -9
- package/query/CriteriaNodeFactory.js +10 -5
- package/query/NativeQueryBuilder.js +1 -1
- package/query/ObjectCriteriaNode.js +30 -7
- package/query/QueryBuilder.d.ts +15 -1
- package/query/QueryBuilder.js +92 -17
- package/query/QueryBuilderHelper.js +4 -10
- package/query/ScalarCriteriaNode.d.ts +3 -3
- package/query/ScalarCriteriaNode.js +7 -5
- package/schema/DatabaseSchema.js +18 -2
- package/schema/DatabaseTable.d.ts +5 -4
- package/schema/DatabaseTable.js +39 -6
- package/schema/SchemaComparator.js +1 -1
- package/schema/SchemaHelper.d.ts +2 -0
- package/schema/SchemaHelper.js +9 -5
- package/schema/SqlSchemaGenerator.d.ts +6 -1
- package/schema/SqlSchemaGenerator.js +25 -5
- package/typings.d.ts +7 -2
package/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: [] };
|
|
@@ -240,9 +240,12 @@ export class QueryBuilder {
|
|
|
240
240
|
}
|
|
241
241
|
}
|
|
242
242
|
prop.targetMeta.props
|
|
243
|
-
.filter(prop =>
|
|
244
|
-
|
|
245
|
-
|
|
243
|
+
.filter(prop => {
|
|
244
|
+
if (!explicitFields) {
|
|
245
|
+
return this.platform.shouldHaveColumn(prop, populate);
|
|
246
|
+
}
|
|
247
|
+
return prop.primary && !explicitFields.includes(prop.name) && !explicitFields.includes(`${alias}.${prop.name}`);
|
|
248
|
+
})
|
|
246
249
|
.forEach(prop => fields.push(...this.driver.mapPropToFieldNames(this, prop, alias)));
|
|
247
250
|
return fields;
|
|
248
251
|
}
|
|
@@ -257,6 +260,40 @@ export class QueryBuilder {
|
|
|
257
260
|
const cond = await this.em.applyFilters(this.mainAlias.entityName, {}, filterOptions, 'read');
|
|
258
261
|
this.andWhere(cond);
|
|
259
262
|
}
|
|
263
|
+
autoJoinedPaths = [];
|
|
264
|
+
/**
|
|
265
|
+
* @internal
|
|
266
|
+
*/
|
|
267
|
+
scheduleFilterCheck(path) {
|
|
268
|
+
this.autoJoinedPaths.push(path);
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* @internal
|
|
272
|
+
*/
|
|
273
|
+
async applyJoinedFilters(em, filterOptions = {}) {
|
|
274
|
+
for (const path of this.autoJoinedPaths) {
|
|
275
|
+
const join = this.getJoinForPath(path);
|
|
276
|
+
if (join.type === JoinType.pivotJoin) {
|
|
277
|
+
continue;
|
|
278
|
+
}
|
|
279
|
+
const cond = await em.applyFilters(join.prop.type, join.cond, filterOptions, 'read');
|
|
280
|
+
if (Utils.hasObjectKeys(cond)) {
|
|
281
|
+
// remove nested filters, we only care about scalars here, nesting would require another join branch
|
|
282
|
+
for (const key of Object.keys(cond)) {
|
|
283
|
+
if (Utils.isPlainObject(cond[key]) && Object.keys(cond[key]).every(k => !(Utils.isOperator(k) && !['$some', '$none', '$every'].includes(k)))) {
|
|
284
|
+
delete cond[key];
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
if (Utils.hasObjectKeys(join.cond)) {
|
|
288
|
+
/* v8 ignore next */
|
|
289
|
+
join.cond = { $and: [join.cond, cond] };
|
|
290
|
+
}
|
|
291
|
+
else {
|
|
292
|
+
join.cond = { ...cond };
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
260
297
|
withSubQuery(subQuery, alias) {
|
|
261
298
|
this.ensureNotFinalized();
|
|
262
299
|
if (subQuery instanceof RawQueryFragment) {
|
|
@@ -335,7 +372,7 @@ export class QueryBuilder {
|
|
|
335
372
|
convertCustomTypes: false,
|
|
336
373
|
type: 'orderBy',
|
|
337
374
|
});
|
|
338
|
-
this._orderBy.push(CriteriaNodeFactory.createNode(this.metadata, this.mainAlias.entityName, processed).process(this, { matchPopulateJoins: true }));
|
|
375
|
+
this._orderBy.push(CriteriaNodeFactory.createNode(this.metadata, this.mainAlias.entityName, processed).process(this, { matchPopulateJoins: true, type: 'orderBy' }));
|
|
339
376
|
});
|
|
340
377
|
return this;
|
|
341
378
|
}
|
|
@@ -647,7 +684,7 @@ export class QueryBuilder {
|
|
|
647
684
|
}
|
|
648
685
|
const query = this.toQuery();
|
|
649
686
|
const cached = await this.em?.tryCache(this.mainAlias.entityName, this._cache, ['qb.execute', query.sql, query.params, method]);
|
|
650
|
-
if (cached?.data) {
|
|
687
|
+
if (cached?.data !== undefined) {
|
|
651
688
|
return cached.data;
|
|
652
689
|
}
|
|
653
690
|
const write = method === 'run' || !this.platform.getConfig().get('preferReadReplicas');
|
|
@@ -845,7 +882,8 @@ export class QueryBuilder {
|
|
|
845
882
|
if (field instanceof RawQueryFragment) {
|
|
846
883
|
field = this.platform.formatQuery(field.sql, field.params);
|
|
847
884
|
}
|
|
848
|
-
|
|
885
|
+
const key = `${this.alias}.${prop.name}#${alias}`;
|
|
886
|
+
this._joins[key] = {
|
|
849
887
|
prop,
|
|
850
888
|
alias,
|
|
851
889
|
type,
|
|
@@ -854,7 +892,7 @@ export class QueryBuilder {
|
|
|
854
892
|
subquery: field.toString(),
|
|
855
893
|
ownerAlias: this.alias,
|
|
856
894
|
};
|
|
857
|
-
return prop;
|
|
895
|
+
return { prop, key };
|
|
858
896
|
}
|
|
859
897
|
if (!subquery && type.includes('lateral')) {
|
|
860
898
|
throw new Error(`Lateral join can be used only with a sub-query.`);
|
|
@@ -879,10 +917,13 @@ export class QueryBuilder {
|
|
|
879
917
|
aliasMap: this.getAliasMap(),
|
|
880
918
|
aliased: [QueryType.SELECT, QueryType.COUNT].includes(this.type),
|
|
881
919
|
});
|
|
920
|
+
const criteriaNode = CriteriaNodeFactory.createNode(this.metadata, prop.targetMeta.className, cond);
|
|
921
|
+
cond = criteriaNode.process(this, { ignoreBranching: true, alias });
|
|
882
922
|
let aliasedName = `${fromAlias}.${prop.name}#${alias}`;
|
|
883
923
|
path ??= `${(Object.values(this._joins).find(j => j.alias === fromAlias)?.path ?? entityName)}.${prop.name}`;
|
|
884
924
|
if (prop.kind === ReferenceKind.ONE_TO_MANY) {
|
|
885
925
|
this._joins[aliasedName] = this.helper.joinOneToReference(prop, fromAlias, alias, type, cond, schema);
|
|
926
|
+
this._joins[aliasedName].path ??= path;
|
|
886
927
|
}
|
|
887
928
|
else if (prop.kind === ReferenceKind.MANY_TO_MANY) {
|
|
888
929
|
let pivotAlias = alias;
|
|
@@ -894,17 +935,18 @@ export class QueryBuilder {
|
|
|
894
935
|
const joins = this.helper.joinManyToManyReference(prop, fromAlias, alias, pivotAlias, type, cond, path, schema);
|
|
895
936
|
Object.assign(this._joins, joins);
|
|
896
937
|
this.createAlias(prop.pivotEntity, pivotAlias);
|
|
938
|
+
this._joins[aliasedName].path ??= path;
|
|
939
|
+
aliasedName = Object.keys(joins)[1];
|
|
897
940
|
}
|
|
898
941
|
else if (prop.kind === ReferenceKind.ONE_TO_ONE) {
|
|
899
942
|
this._joins[aliasedName] = this.helper.joinOneToReference(prop, fromAlias, alias, type, cond, schema);
|
|
943
|
+
this._joins[aliasedName].path ??= path;
|
|
900
944
|
}
|
|
901
945
|
else { // MANY_TO_ONE
|
|
902
946
|
this._joins[aliasedName] = this.helper.joinManyToOneReference(prop, fromAlias, alias, type, cond, schema);
|
|
947
|
+
this._joins[aliasedName].path ??= path;
|
|
903
948
|
}
|
|
904
|
-
|
|
905
|
-
this._joins[aliasedName].path = path;
|
|
906
|
-
}
|
|
907
|
-
return prop;
|
|
949
|
+
return { prop, key: aliasedName };
|
|
908
950
|
}
|
|
909
951
|
prepareFields(fields, type = 'where') {
|
|
910
952
|
const ret = [];
|
|
@@ -1079,6 +1121,7 @@ export class QueryBuilder {
|
|
|
1079
1121
|
const meta = this.mainAlias.metadata;
|
|
1080
1122
|
this.applyDiscriminatorCondition();
|
|
1081
1123
|
this.processPopulateHint();
|
|
1124
|
+
this.processNestedJoins();
|
|
1082
1125
|
if (meta && (this._fields?.includes('*') || this._fields?.includes(`${this.mainAlias.aliasName}.*`))) {
|
|
1083
1126
|
meta.props
|
|
1084
1127
|
.filter(prop => prop.formula && (!prop.lazy || this.flags.has(QueryFlag.INCLUDE_LAZY_FORMULAS)))
|
|
@@ -1138,6 +1181,7 @@ export class QueryBuilder {
|
|
|
1138
1181
|
this._joins[aliasedName] = this.helper.joinOneToReference(prop, this.mainAlias.aliasName, alias, JoinType.leftJoin);
|
|
1139
1182
|
this._joins[aliasedName].path = `${(Object.values(this._joins).find(j => j.alias === fromAlias)?.path ?? meta.className)}.${prop.name}`;
|
|
1140
1183
|
this._populateMap[aliasedName] = this._joins[aliasedName].alias;
|
|
1184
|
+
this.createAlias(prop.type, alias);
|
|
1141
1185
|
}
|
|
1142
1186
|
});
|
|
1143
1187
|
this.processPopulateWhere(false);
|
|
@@ -1152,12 +1196,12 @@ export class QueryBuilder {
|
|
|
1152
1196
|
let joins = Object.values(this._joins);
|
|
1153
1197
|
for (const join of joins) {
|
|
1154
1198
|
join.cond_ ??= join.cond;
|
|
1155
|
-
join.cond =
|
|
1199
|
+
join.cond = { ...join.cond };
|
|
1156
1200
|
}
|
|
1157
1201
|
if (typeof this[key] === 'object') {
|
|
1158
1202
|
const cond = CriteriaNodeFactory
|
|
1159
1203
|
.createNode(this.metadata, this.mainAlias.entityName, this[key])
|
|
1160
|
-
.process(this, { matchPopulateJoins: true, ignoreBranching: true, preferNoBranch: true });
|
|
1204
|
+
.process(this, { matchPopulateJoins: true, ignoreBranching: true, preferNoBranch: true, filter });
|
|
1161
1205
|
// there might be new joins created by processing the `populateWhere` object
|
|
1162
1206
|
joins = Object.values(this._joins);
|
|
1163
1207
|
this.mergeOnConditions(joins, cond, filter);
|
|
@@ -1198,10 +1242,41 @@ export class QueryBuilder {
|
|
|
1198
1242
|
}
|
|
1199
1243
|
}
|
|
1200
1244
|
}
|
|
1245
|
+
/**
|
|
1246
|
+
* When adding an inner join on a left joined relation, we need to nest them,
|
|
1247
|
+
* otherwise the inner join could discard rows of the root table.
|
|
1248
|
+
*/
|
|
1249
|
+
processNestedJoins() {
|
|
1250
|
+
if (this.flags.has(QueryFlag.DISABLE_NESTED_INNER_JOIN)) {
|
|
1251
|
+
return;
|
|
1252
|
+
}
|
|
1253
|
+
const joins = Object.values(this._joins);
|
|
1254
|
+
const lookupParentGroup = (j) => {
|
|
1255
|
+
return j.nested ?? (j.parent ? lookupParentGroup(j.parent) : undefined);
|
|
1256
|
+
};
|
|
1257
|
+
for (const join of joins) {
|
|
1258
|
+
if (join.type === JoinType.innerJoin) {
|
|
1259
|
+
join.parent = joins.find(j => j.alias === join.ownerAlias);
|
|
1260
|
+
// https://stackoverflow.com/a/56815807/3665878
|
|
1261
|
+
if (join.parent?.type === JoinType.leftJoin || join.parent?.type === JoinType.nestedLeftJoin) {
|
|
1262
|
+
const nested = ((join.parent).nested ??= new Set());
|
|
1263
|
+
join.type = join.type === JoinType.innerJoin
|
|
1264
|
+
? JoinType.nestedInnerJoin
|
|
1265
|
+
: JoinType.nestedLeftJoin;
|
|
1266
|
+
nested.add(join);
|
|
1267
|
+
}
|
|
1268
|
+
else if (join.parent?.type === JoinType.nestedInnerJoin) {
|
|
1269
|
+
const group = lookupParentGroup(join.parent);
|
|
1270
|
+
join.type = join.type === JoinType.innerJoin
|
|
1271
|
+
? JoinType.nestedInnerJoin
|
|
1272
|
+
: JoinType.nestedLeftJoin;
|
|
1273
|
+
group?.add(join);
|
|
1274
|
+
}
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
1277
|
+
}
|
|
1201
1278
|
hasToManyJoins() {
|
|
1202
|
-
// console.log(this._joins);
|
|
1203
1279
|
return Object.values(this._joins).some(join => {
|
|
1204
|
-
// console.log(join.prop.name, join.prop.kind, [ReferenceKind.ONE_TO_MANY, ReferenceKind.MANY_TO_MANY].includes(join.prop.kind));
|
|
1205
1280
|
return [ReferenceKind.ONE_TO_MANY, ReferenceKind.MANY_TO_MANY].includes(join.prop.kind);
|
|
1206
1281
|
});
|
|
1207
1282
|
}
|
|
@@ -39,9 +39,6 @@ export class QueryBuilderHelper {
|
|
|
39
39
|
const prop = this.getProperty(f, a);
|
|
40
40
|
const fkIdx2 = prop?.fieldNames.findIndex(name => name === f) ?? -1;
|
|
41
41
|
if (fkIdx2 !== -1) {
|
|
42
|
-
if (prop?.ownColumns && !prop.ownColumns.includes(f)) {
|
|
43
|
-
continue;
|
|
44
|
-
}
|
|
45
42
|
parts.push(this.mapper(a !== this.alias ? `${a}.${prop.fieldNames[fkIdx2]}` : prop.fieldNames[fkIdx2], type, value, alias));
|
|
46
43
|
}
|
|
47
44
|
else if (prop) {
|
|
@@ -62,9 +59,6 @@ export class QueryBuilderHelper {
|
|
|
62
59
|
}
|
|
63
60
|
});
|
|
64
61
|
}
|
|
65
|
-
if (parts.length === 1) {
|
|
66
|
-
return parts[0];
|
|
67
|
-
}
|
|
68
62
|
return raw('(' + parts.map(part => this.platform.quoteIdentifier(part)).join(', ') + ')');
|
|
69
63
|
}
|
|
70
64
|
const rawField = RawQueryFragment.getKnownFragment(field);
|
|
@@ -364,7 +358,7 @@ export class QueryBuilderHelper {
|
|
|
364
358
|
return;
|
|
365
359
|
}
|
|
366
360
|
parts.push(operator === '$or' ? `(${res.sql})` : res.sql);
|
|
367
|
-
params.push(
|
|
361
|
+
res.params.forEach(p => params.push(p));
|
|
368
362
|
}
|
|
369
363
|
appendQuerySubCondition(type, cond, key) {
|
|
370
364
|
const parts = [];
|
|
@@ -473,8 +467,8 @@ export class QueryBuilderHelper {
|
|
|
473
467
|
parts.push(sql);
|
|
474
468
|
params.push(...params2);
|
|
475
469
|
}
|
|
476
|
-
else if (
|
|
477
|
-
parts.push(`1 = 0`);
|
|
470
|
+
else if (['$in', '$nin'].includes(op) && Array.isArray(value[op]) && value[op].length === 0) {
|
|
471
|
+
parts.push(`1 = ${op === '$in' ? 0 : 1}`);
|
|
478
472
|
}
|
|
479
473
|
else if (value[op] instanceof RawQueryFragment || value[op] instanceof NativeQueryBuilder) {
|
|
480
474
|
const query = value[op] instanceof NativeQueryBuilder ? value[op].toRaw() : value[op];
|
|
@@ -508,7 +502,7 @@ export class QueryBuilderHelper {
|
|
|
508
502
|
params.push(item);
|
|
509
503
|
}
|
|
510
504
|
else {
|
|
511
|
-
params.push(
|
|
505
|
+
value.forEach(v => params.push(v));
|
|
512
506
|
}
|
|
513
507
|
return `(${value.map(() => '?').join(', ')})`;
|
|
514
508
|
}
|
|
@@ -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) {
|
package/schema/DatabaseSchema.js
CHANGED
|
@@ -69,6 +69,7 @@ export class DatabaseSchema {
|
|
|
69
69
|
static fromMetadata(metadata, platform, config, schemaName) {
|
|
70
70
|
const schema = new DatabaseSchema(platform, schemaName ?? config.get('schema'));
|
|
71
71
|
const nativeEnums = {};
|
|
72
|
+
const skipColumns = config.get('schemaGenerator').skipColumns || {};
|
|
72
73
|
for (const meta of metadata) {
|
|
73
74
|
for (const prop of meta.props) {
|
|
74
75
|
if (prop.nativeEnumName) {
|
|
@@ -97,7 +98,7 @@ export class DatabaseSchema {
|
|
|
97
98
|
const table = schema.addTable(meta.collection, this.getSchemaName(meta, config, schemaName));
|
|
98
99
|
table.comment = meta.comment;
|
|
99
100
|
for (const prop of meta.props) {
|
|
100
|
-
if (!this.shouldHaveColumn(meta, prop)) {
|
|
101
|
+
if (!this.shouldHaveColumn(meta, prop, skipColumns)) {
|
|
101
102
|
continue;
|
|
102
103
|
}
|
|
103
104
|
table.addColumnFromProperty(prop, meta, config);
|
|
@@ -129,10 +130,25 @@ export class DatabaseSchema {
|
|
|
129
130
|
return ((takeTables?.some(tableNameToMatch => this.matchName(tableName, tableNameToMatch)) ?? true) &&
|
|
130
131
|
!(skipTables?.some(tableNameToMatch => this.matchName(tableName, tableNameToMatch)) ?? false));
|
|
131
132
|
}
|
|
132
|
-
static shouldHaveColumn(meta, prop) {
|
|
133
|
+
static shouldHaveColumn(meta, prop, skipColumns) {
|
|
133
134
|
if (prop.persist === false || (prop.columnTypes?.length ?? 0) === 0) {
|
|
134
135
|
return false;
|
|
135
136
|
}
|
|
137
|
+
// Check if column should be skipped
|
|
138
|
+
if (skipColumns) {
|
|
139
|
+
const tableName = meta.tableName;
|
|
140
|
+
const tableSchema = meta.schema;
|
|
141
|
+
const fullTableName = tableSchema ? `${tableSchema}.${tableName}` : tableName;
|
|
142
|
+
// Check for skipColumns by table name or fully qualified table name
|
|
143
|
+
const columnsToSkip = skipColumns[tableName] || skipColumns[fullTableName];
|
|
144
|
+
if (columnsToSkip) {
|
|
145
|
+
for (const fieldName of prop.fieldNames) {
|
|
146
|
+
if (columnsToSkip.some(pattern => this.matchName(fieldName, pattern))) {
|
|
147
|
+
return false;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
136
152
|
if (prop.kind === ReferenceKind.EMBEDDED && prop.object) {
|
|
137
153
|
return true;
|
|
138
154
|
}
|
|
@@ -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
|
*/
|
|
@@ -97,11 +97,14 @@ export class DatabaseTable {
|
|
|
97
97
|
ignoreSchemaChanges: prop.ignoreSchemaChanges,
|
|
98
98
|
};
|
|
99
99
|
this.columns[field].unsigned ??= this.columns[field].autoincrement;
|
|
100
|
+
if (this.nativeEnums[type]) {
|
|
101
|
+
this.columns[field].enumItems ??= this.nativeEnums[type].items;
|
|
102
|
+
}
|
|
100
103
|
const defaultValue = this.platform.getSchemaHelper().normalizeDefaultValue(prop.defaultRaw, prop.length);
|
|
101
104
|
this.columns[field].default = defaultValue;
|
|
102
105
|
});
|
|
103
106
|
if ([ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind)) {
|
|
104
|
-
const constraintName = this.getIndexName(true, prop.fieldNames, 'foreign');
|
|
107
|
+
const constraintName = this.getIndexName(prop.foreignKeyName ?? true, prop.fieldNames, 'foreign');
|
|
105
108
|
let schema = prop.targetMeta.root.schema === '*' ? this.schema : (prop.targetMeta.root.schema ?? config.get('schema', this.platform.getDefaultSchemaName()));
|
|
106
109
|
if (prop.referencedTableName.includes('.')) {
|
|
107
110
|
schema = undefined;
|
|
@@ -112,6 +115,7 @@ export class DatabaseTable {
|
|
|
112
115
|
localTableName: this.getShortestName(false),
|
|
113
116
|
referencedColumnNames: prop.referencedColumnNames,
|
|
114
117
|
referencedTableName: schema ? `${schema}.${prop.referencedTableName}` : prop.referencedTableName,
|
|
118
|
+
createForeignKeyConstraint: prop.createForeignKeyConstraint,
|
|
115
119
|
};
|
|
116
120
|
const cascade = prop.cascade.includes(Cascade.REMOVE) || prop.cascade.includes(Cascade.ALL);
|
|
117
121
|
if (prop.deleteRule || cascade || prop.nullable) {
|
|
@@ -697,18 +701,47 @@ export class DatabaseTable {
|
|
|
697
701
|
}
|
|
698
702
|
return '' + val;
|
|
699
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
|
+
}
|
|
700
721
|
addIndex(meta, index, type) {
|
|
701
722
|
const properties = Utils.unique(Utils.flatten(Utils.asArray(index.properties).map(prop => {
|
|
702
|
-
const
|
|
723
|
+
const parts = prop.split('.');
|
|
724
|
+
const root = parts[0];
|
|
703
725
|
if (meta.properties[prop]) {
|
|
704
726
|
if (meta.properties[prop].embeddedPath) {
|
|
705
727
|
return [meta.properties[prop].embeddedPath.join('.')];
|
|
706
728
|
}
|
|
707
729
|
return meta.properties[prop].fieldNames;
|
|
708
730
|
}
|
|
731
|
+
const rootProp = meta.properties[root];
|
|
732
|
+
// inline embedded property index, we need to find the field name of the child property
|
|
733
|
+
if (rootProp?.embeddable && !rootProp.object && parts.length > 1) {
|
|
734
|
+
const expand = (p, i) => {
|
|
735
|
+
if (parts.length === i) {
|
|
736
|
+
return p.fieldNames[0];
|
|
737
|
+
}
|
|
738
|
+
return expand(p.embeddedProps[parts[i]], i + 1);
|
|
739
|
+
};
|
|
740
|
+
return [expand(rootProp, 1)];
|
|
741
|
+
}
|
|
709
742
|
// json index, we need to rename the column only
|
|
710
|
-
if (
|
|
711
|
-
return [prop.replace(root,
|
|
743
|
+
if (rootProp) {
|
|
744
|
+
return [prop.replace(root, rootProp.fieldNames[0])];
|
|
712
745
|
}
|
|
713
746
|
/* v8 ignore next */
|
|
714
747
|
return [prop];
|
|
@@ -726,7 +759,7 @@ export class DatabaseTable {
|
|
|
726
759
|
primary: type === 'primary',
|
|
727
760
|
unique: type !== 'index',
|
|
728
761
|
type: index.type,
|
|
729
|
-
expression: index.expression,
|
|
762
|
+
expression: this.processIndexExpression(name, index.expression, meta),
|
|
730
763
|
options: index.options,
|
|
731
764
|
deferMode: index.deferMode,
|
|
732
765
|
});
|
|
@@ -511,7 +511,7 @@ export class SchemaComparator {
|
|
|
511
511
|
return str
|
|
512
512
|
?.replace(/_\w+'(.*?)'/g, '$1')
|
|
513
513
|
.replace(/in\s*\((.*?)\)/ig, '= any (array[$1])')
|
|
514
|
-
.replace(/['"`()]|::\w+| +/g, '')
|
|
514
|
+
.replace(/['"`()\n[\]]|::\w+| +/g, '')
|
|
515
515
|
.replace(/anyarray\[(.*)]/ig, '$1')
|
|
516
516
|
.toLowerCase();
|
|
517
517
|
};
|
package/schema/SchemaHelper.d.ts
CHANGED
|
@@ -63,6 +63,8 @@ export declare abstract class SchemaHelper {
|
|
|
63
63
|
disableForeignKeys?: boolean;
|
|
64
64
|
createForeignKeyConstraints?: boolean;
|
|
65
65
|
ignoreSchema?: string[];
|
|
66
|
+
skipTables?: (string | RegExp)[];
|
|
67
|
+
skipColumns?: Dictionary<(string | RegExp)[]>;
|
|
66
68
|
managementDbName?: string;
|
|
67
69
|
};
|
|
68
70
|
protected processComment(comment: string): string;
|
package/schema/SchemaHelper.js
CHANGED
|
@@ -119,9 +119,8 @@ export class SchemaHelper {
|
|
|
119
119
|
}
|
|
120
120
|
}
|
|
121
121
|
Utils.removeDuplicates(changedNativeEnums).forEach(([enumName, itemsNew, itemsOld]) => {
|
|
122
|
-
// postgres allows only adding new items
|
|
123
|
-
|
|
124
|
-
const newItems = itemsNew.filter(val => !itemsOld.includes(val.toLowerCase()));
|
|
122
|
+
// postgres allows only adding new items
|
|
123
|
+
const newItems = itemsNew.filter(val => !itemsOld.includes(val));
|
|
125
124
|
if (enumName.includes('.')) {
|
|
126
125
|
const [enumSchemaName, rawEnumName] = enumName.split('.');
|
|
127
126
|
ret.push(...newItems.map(val => this.getAlterNativeEnumSQL(rawEnumName, enumSchemaName, val, itemsNew, itemsOld)));
|
|
@@ -264,7 +263,12 @@ export class SchemaHelper {
|
|
|
264
263
|
if (column.autoincrement && !column.generated && !compositePK && (!changedProperties || changedProperties.has('autoincrement') || changedProperties.has('type'))) {
|
|
265
264
|
Utils.runIfNotEmpty(() => col.push('primary key'), primaryKey && column.primary);
|
|
266
265
|
}
|
|
267
|
-
|
|
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
|
+
}
|
|
268
272
|
Utils.runIfNotEmpty(() => col.push(column.extra), column.extra);
|
|
269
273
|
Utils.runIfNotEmpty(() => col.push(`comment ${this.platform.quoteValue(column.comment)}`), column.comment);
|
|
270
274
|
return col.join(' ');
|
|
@@ -422,7 +426,7 @@ export class SchemaHelper {
|
|
|
422
426
|
return `alter table ${table.getQuotedName()} comment = ${this.platform.quoteValue(comment ?? '')}`;
|
|
423
427
|
}
|
|
424
428
|
createForeignKey(table, foreignKey, alterTable = true, inline = false) {
|
|
425
|
-
if (!this.options.createForeignKeyConstraints) {
|
|
429
|
+
if (!this.options.createForeignKeyConstraints || !foreignKey.createForeignKeyConstraint) {
|
|
426
430
|
return '';
|
|
427
431
|
}
|
|
428
432
|
const constraintName = this.quote(foreignKey.constraintName);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AbstractSchemaGenerator, type ClearDatabaseOptions, type CreateSchemaOptions, type DropSchemaOptions, type EnsureDatabaseOptions, type ISchemaGenerator, type MikroORM, type Transaction, type UpdateSchemaOptions } from '@mikro-orm/core';
|
|
1
|
+
import { AbstractSchemaGenerator, type ClearDatabaseOptions, type CreateSchemaOptions, type Dictionary, type DropSchemaOptions, type EnsureDatabaseOptions, type EntityMetadata, type ISchemaGenerator, type MikroORM, type Transaction, type UpdateSchemaOptions } from '@mikro-orm/core';
|
|
2
2
|
import type { SchemaDifference } from '../typings.js';
|
|
3
3
|
import { DatabaseSchema } from './DatabaseSchema.js';
|
|
4
4
|
import type { AbstractSqlDriver } from '../AbstractSqlDriver.js';
|
|
@@ -8,6 +8,8 @@ export declare class SqlSchemaGenerator extends AbstractSchemaGenerator<Abstract
|
|
|
8
8
|
disableForeignKeys?: boolean;
|
|
9
9
|
createForeignKeyConstraints?: boolean;
|
|
10
10
|
ignoreSchema?: string[];
|
|
11
|
+
skipTables?: (string | RegExp)[];
|
|
12
|
+
skipColumns?: Dictionary<(string | RegExp)[]>;
|
|
11
13
|
managementDbName?: string;
|
|
12
14
|
};
|
|
13
15
|
protected lastEnsuredDatabase?: string;
|
|
@@ -18,6 +20,7 @@ export declare class SqlSchemaGenerator extends AbstractSchemaGenerator<Abstract
|
|
|
18
20
|
*/
|
|
19
21
|
ensureDatabase(options?: EnsureDatabaseOptions): Promise<boolean>;
|
|
20
22
|
getTargetSchema(schema?: string): DatabaseSchema;
|
|
23
|
+
protected getOrderedMetadata(schema?: string): EntityMetadata[];
|
|
21
24
|
getCreateSchemaSQL(options?: CreateSchemaOptions): Promise<string>;
|
|
22
25
|
dropSchema(options?: DropSchemaOptions): Promise<void>;
|
|
23
26
|
createNamespace(name: string): Promise<void>;
|
|
@@ -54,5 +57,7 @@ export declare class SqlSchemaGenerator extends AbstractSchemaGenerator<Abstract
|
|
|
54
57
|
dropTableIfExists(name: string, schema?: string): Promise<void>;
|
|
55
58
|
private wrapSchema;
|
|
56
59
|
private append;
|
|
60
|
+
private matchName;
|
|
61
|
+
private isTableSkipped;
|
|
57
62
|
}
|
|
58
63
|
export { SqlSchemaGenerator as SchemaGenerator };
|
|
@@ -47,6 +47,15 @@ export class SqlSchemaGenerator extends AbstractSchemaGenerator {
|
|
|
47
47
|
const schemaName = schema ?? this.config.get('schema') ?? this.platform.getDefaultSchemaName();
|
|
48
48
|
return DatabaseSchema.fromMetadata(metadata, this.platform, this.config, schemaName);
|
|
49
49
|
}
|
|
50
|
+
getOrderedMetadata(schema) {
|
|
51
|
+
const metadata = super.getOrderedMetadata(schema);
|
|
52
|
+
// Filter out skipped tables
|
|
53
|
+
return metadata.filter(meta => {
|
|
54
|
+
const tableName = meta.tableName;
|
|
55
|
+
const tableSchema = meta.schema ?? schema ?? this.config.get('schema');
|
|
56
|
+
return !this.isTableSkipped(tableName, tableSchema);
|
|
57
|
+
});
|
|
58
|
+
}
|
|
50
59
|
async getCreateSchemaSQL(options = {}) {
|
|
51
60
|
const toSchema = this.getTargetSchema(options.schema);
|
|
52
61
|
const ret = [];
|
|
@@ -103,9 +112,10 @@ export class SqlSchemaGenerator extends AbstractSchemaGenerator {
|
|
|
103
112
|
return super.clearDatabase(options);
|
|
104
113
|
}
|
|
105
114
|
await this.execute(this.helper.disableForeignKeysSQL());
|
|
106
|
-
|
|
115
|
+
const schema = options?.schema ?? this.config.get('schema', this.platform.getDefaultSchemaName());
|
|
116
|
+
for (const meta of this.getOrderedMetadata(schema).reverse()) {
|
|
107
117
|
await this.driver.createQueryBuilder(meta.className, this.em?.getTransactionContext(), 'write', false)
|
|
108
|
-
.withSchema(
|
|
118
|
+
.withSchema(schema)
|
|
109
119
|
.truncate()
|
|
110
120
|
.execute();
|
|
111
121
|
}
|
|
@@ -283,7 +293,6 @@ export class SqlSchemaGenerator extends AbstractSchemaGenerator {
|
|
|
283
293
|
name ??= this.config.get('dbName');
|
|
284
294
|
const sql = this.helper.getCreateDatabaseSQL('' + this.platform.quoteIdentifier(name));
|
|
285
295
|
if (sql) {
|
|
286
|
-
// console.log(sql);
|
|
287
296
|
await this.execute(sql);
|
|
288
297
|
}
|
|
289
298
|
this.config.set('dbName', name);
|
|
@@ -317,12 +326,10 @@ export class SqlSchemaGenerator extends AbstractSchemaGenerator {
|
|
|
317
326
|
if (this.platform.supportsMultipleStatements()) {
|
|
318
327
|
for (const group of groups) {
|
|
319
328
|
const query = group.join('\n');
|
|
320
|
-
// console.log(query);
|
|
321
329
|
await this.driver.execute(query);
|
|
322
330
|
}
|
|
323
331
|
return;
|
|
324
332
|
}
|
|
325
|
-
// console.log(groups);
|
|
326
333
|
await Utils.runSerial(groups.flat(), line => this.driver.execute(line));
|
|
327
334
|
}
|
|
328
335
|
async dropTableIfExists(name, schema) {
|
|
@@ -348,6 +355,19 @@ export class SqlSchemaGenerator extends AbstractSchemaGenerator {
|
|
|
348
355
|
append(array, sql, pad) {
|
|
349
356
|
return this.helper.append(array, sql, pad);
|
|
350
357
|
}
|
|
358
|
+
matchName(name, nameToMatch) {
|
|
359
|
+
return typeof nameToMatch === 'string'
|
|
360
|
+
? name.toLocaleLowerCase() === nameToMatch.toLocaleLowerCase()
|
|
361
|
+
: nameToMatch.test(name);
|
|
362
|
+
}
|
|
363
|
+
isTableSkipped(tableName, schemaName) {
|
|
364
|
+
const skipTables = this.options.skipTables;
|
|
365
|
+
if (!skipTables || skipTables.length === 0) {
|
|
366
|
+
return false;
|
|
367
|
+
}
|
|
368
|
+
const fullTableName = schemaName ? `${schemaName}.${tableName}` : tableName;
|
|
369
|
+
return skipTables.some(pattern => this.matchName(tableName, pattern) || this.matchName(fullTableName, pattern));
|
|
370
|
+
}
|
|
351
371
|
}
|
|
352
372
|
// for back compatibility
|
|
353
373
|
export { SqlSchemaGenerator as SchemaGenerator };
|