@mikro-orm/sql 7.0.0-dev.299 → 7.0.0-dev.301
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.js +2 -1
- package/AbstractSqlDriver.js +135 -39
- package/AbstractSqlPlatform.js +2 -3
- package/PivotCollectionPersister.js +2 -2
- package/SqlEntityManager.d.ts +1 -1
- package/SqlEntityManager.js +5 -5
- package/dialects/mssql/MsSqlNativeQueryBuilder.js +3 -4
- package/dialects/mysql/BaseMySqlPlatform.js +1 -2
- package/dialects/mysql/MySqlSchemaHelper.js +21 -10
- package/dialects/postgresql/BasePostgreSqlPlatform.js +38 -30
- package/dialects/postgresql/PostgreSqlSchemaHelper.js +55 -45
- package/dialects/sqlite/BaseSqliteConnection.js +2 -2
- package/dialects/sqlite/NodeSqliteDialect.js +3 -1
- package/dialects/sqlite/SqlitePlatform.js +4 -1
- package/dialects/sqlite/SqliteSchemaHelper.js +11 -9
- package/package.json +29 -29
- package/plugin/transformer.js +17 -16
- package/query/CriteriaNode.js +28 -10
- package/query/CriteriaNodeFactory.js +5 -1
- package/query/NativeQueryBuilder.js +1 -1
- package/query/ObjectCriteriaNode.js +67 -43
- package/query/QueryBuilder.d.ts +25 -12
- package/query/QueryBuilder.js +105 -49
- package/query/QueryBuilderHelper.js +41 -14
- package/query/ScalarCriteriaNode.js +13 -6
- package/query/raw.js +1 -1
- package/schema/DatabaseSchema.js +21 -15
- package/schema/DatabaseTable.js +81 -51
- package/schema/SchemaComparator.js +54 -30
- package/schema/SchemaHelper.js +28 -8
- package/schema/SqlSchemaGenerator.js +13 -7
- package/tsconfig.build.tsbuildinfo +1 -1
- package/typings.d.ts +1 -1
package/schema/DatabaseTable.js
CHANGED
|
@@ -80,10 +80,13 @@ export class DatabaseTable {
|
|
|
80
80
|
this.columns[field] = {
|
|
81
81
|
name: prop.fieldNames[idx],
|
|
82
82
|
type: prop.columnTypes[idx],
|
|
83
|
-
generated: prop.generated instanceof RawQueryFragment
|
|
83
|
+
generated: prop.generated instanceof RawQueryFragment
|
|
84
|
+
? this.platform.formatQuery(prop.generated.sql, prop.generated.params)
|
|
85
|
+
: prop.generated,
|
|
84
86
|
mappedType,
|
|
85
87
|
unsigned: prop.unsigned && this.platform.isNumericColumn(mappedType),
|
|
86
|
-
autoincrement: prop.autoincrement ??
|
|
88
|
+
autoincrement: prop.autoincrement ??
|
|
89
|
+
(primary && prop.kind === ReferenceKind.SCALAR && this.platform.isNumericColumn(mappedType)),
|
|
87
90
|
primary,
|
|
88
91
|
nullable: this.columns[field]?.nullable ?? !!prop.nullable,
|
|
89
92
|
nativeEnumName: prop.nativeEnumName,
|
|
@@ -105,7 +108,9 @@ export class DatabaseTable {
|
|
|
105
108
|
});
|
|
106
109
|
if ([ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind) && !prop.polymorphic) {
|
|
107
110
|
const constraintName = this.getIndexName(prop.foreignKeyName ?? true, prop.fieldNames, 'foreign');
|
|
108
|
-
let schema = prop.targetMeta.root.schema === '*'
|
|
111
|
+
let schema = prop.targetMeta.root.schema === '*'
|
|
112
|
+
? this.schema
|
|
113
|
+
: (prop.targetMeta.root.schema ?? config.get('schema', this.platform.getDefaultSchemaName()));
|
|
109
114
|
if (prop.referencedTableName.includes('.')) {
|
|
110
115
|
schema = undefined;
|
|
111
116
|
}
|
|
@@ -154,19 +159,20 @@ export class DatabaseTable {
|
|
|
154
159
|
return this.platform.getIndexName(this.name, columnNames, type);
|
|
155
160
|
}
|
|
156
161
|
getEntityDeclaration(namingStrategy, schemaHelper, scalarPropertiesForRelations) {
|
|
157
|
-
const { fksOnColumnProps, fksOnStandaloneProps, columnFks, fkIndexes, nullableForeignKeys, skippedColumnNames
|
|
162
|
+
const { fksOnColumnProps, fksOnStandaloneProps, columnFks, fkIndexes, nullableForeignKeys, skippedColumnNames } = this.foreignKeysToProps(namingStrategy, scalarPropertiesForRelations);
|
|
158
163
|
const name = namingStrategy.getEntityName(this.name, this.schema);
|
|
159
164
|
const schema = new EntitySchema({ name, collection: this.name, schema: this.schema, comment: this.comment });
|
|
160
165
|
const compositeFkIndexes = {};
|
|
161
166
|
const compositeFkUniques = {};
|
|
162
|
-
const potentiallyUnmappedIndexes = this.indexes.filter(index => !index.primary // Skip primary index. Whether it's in use by scalar column or FK, it's already mapped.
|
|
163
|
-
|
|
164
|
-
index.columnNames.length > 1 // All composite indexes are to be mapped to entity decorators or FK props.
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
167
|
+
const potentiallyUnmappedIndexes = this.indexes.filter(index => !index.primary && // Skip primary index. Whether it's in use by scalar column or FK, it's already mapped.
|
|
168
|
+
// Non-trivial non-composite indexes will be declared at the entity's metadata, though later outputted in the property
|
|
169
|
+
(index.columnNames.length > 1 || // All composite indexes are to be mapped to entity decorators or FK props.
|
|
170
|
+
skippedColumnNames.includes(index.columnNames[0]) || // Non-composite indexes for skipped columns are to be mapped as entity decorators.
|
|
171
|
+
index.deferMode ||
|
|
172
|
+
index.expression ||
|
|
173
|
+
!(index.columnNames[0] in columnFks)) && // Trivial non-composite indexes for scalar props are to be mapped to the column.
|
|
168
174
|
// ignore indexes that don't have all column names (this can happen in sqlite where there is no way to infer this for expressions)
|
|
169
|
-
|
|
175
|
+
!(index.columnNames.some(col => !col) && !index.expression));
|
|
170
176
|
// Helper to map column name to property name
|
|
171
177
|
const columnToPropertyName = (colName) => this.getPropertyName(namingStrategy, colName);
|
|
172
178
|
for (const index of potentiallyUnmappedIndexes) {
|
|
@@ -198,8 +204,13 @@ export class DatabaseTable {
|
|
|
198
204
|
}
|
|
199
205
|
}
|
|
200
206
|
// An index is trivial if it has no special options that require entity-level declaration
|
|
201
|
-
const hasAdvancedOptions = index.columns?.length ||
|
|
202
|
-
index.
|
|
207
|
+
const hasAdvancedOptions = index.columns?.length ||
|
|
208
|
+
index.include?.length ||
|
|
209
|
+
index.fillFactor ||
|
|
210
|
+
index.type ||
|
|
211
|
+
index.invisible ||
|
|
212
|
+
index.disabled ||
|
|
213
|
+
index.clustered;
|
|
203
214
|
const isTrivial = !index.deferMode && !index.expression && !hasAdvancedOptions;
|
|
204
215
|
if (isTrivial) {
|
|
205
216
|
// Index is for FK. Map to the FK prop and move on.
|
|
@@ -213,7 +224,8 @@ export class DatabaseTable {
|
|
|
213
224
|
}
|
|
214
225
|
}
|
|
215
226
|
}
|
|
216
|
-
const properties = ret.properties ??
|
|
227
|
+
const properties = ret.properties ??
|
|
228
|
+
this.getIndexProperties(index, columnFks, fksOnColumnProps, fksOnStandaloneProps, namingStrategy);
|
|
217
229
|
// If there is a column that cannot be unambiguously mapped to a prop, render an expression.
|
|
218
230
|
if (typeof properties === 'undefined') {
|
|
219
231
|
ret.expression ??= schemaHelper.getCreateIndexSQL(this.name, index);
|
|
@@ -237,7 +249,7 @@ export class DatabaseTable {
|
|
|
237
249
|
}
|
|
238
250
|
schema.addIndex(ret);
|
|
239
251
|
}
|
|
240
|
-
const addedStandaloneFkPropsBasedOnColumn = new Set;
|
|
252
|
+
const addedStandaloneFkPropsBasedOnColumn = new Set();
|
|
241
253
|
const nonSkippedColumns = this.getColumns().filter(column => !skippedColumnNames.includes(column.name));
|
|
242
254
|
for (const column of nonSkippedColumns) {
|
|
243
255
|
const columnName = column.name;
|
|
@@ -259,10 +271,10 @@ export class DatabaseTable {
|
|
|
259
271
|
schema.addProperty(prop.name, prop.type, prop);
|
|
260
272
|
}
|
|
261
273
|
const meta = schema.init().meta;
|
|
262
|
-
const oneToOneCandidateProperties = meta.relations
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
274
|
+
const oneToOneCandidateProperties = meta.relations.filter(prop => prop.primary && prop.kind === ReferenceKind.MANY_TO_ONE);
|
|
275
|
+
if (oneToOneCandidateProperties.length === 1 &&
|
|
276
|
+
oneToOneCandidateProperties[0].fieldNames.length ===
|
|
277
|
+
new Set(meta.getPrimaryProps().flatMap(prop => prop.fieldNames)).size) {
|
|
266
278
|
oneToOneCandidateProperties[0].kind = ReferenceKind.ONE_TO_ONE;
|
|
267
279
|
}
|
|
268
280
|
return meta;
|
|
@@ -277,7 +289,8 @@ export class DatabaseTable {
|
|
|
277
289
|
const standaloneFksBasedOnColumnNames = new Map();
|
|
278
290
|
for (const currentFk of fks) {
|
|
279
291
|
const fkIndex = this.findFkIndex(currentFk);
|
|
280
|
-
if (currentFk.columnNames.length === 1 &&
|
|
292
|
+
if (currentFk.columnNames.length === 1 &&
|
|
293
|
+
!fks.some(fk => fk !== currentFk && fk.columnNames.length === 1 && currentFk.columnNames[0] === fk.columnNames[0])) {
|
|
281
294
|
// Non-composite FK is the only possible one for a column. Render the column with it.
|
|
282
295
|
const columnName = currentFk.columnNames[0];
|
|
283
296
|
columnFks[columnName] ??= [];
|
|
@@ -316,7 +329,10 @@ export class DatabaseTable {
|
|
|
316
329
|
if (nullableColumnsInFk.length > 0) {
|
|
317
330
|
nullableForeignKeys.add(currentFk);
|
|
318
331
|
}
|
|
319
|
-
if (specificColumnNames.length === 1 &&
|
|
332
|
+
if (specificColumnNames.length === 1 &&
|
|
333
|
+
(nullableColumnsInFk.length === currentFk.columnNames.length ||
|
|
334
|
+
nullableColumnsInFk.length === 0 ||
|
|
335
|
+
(nullableColumnsInFk.length === 1 && nullableColumnsInFk[0] === specificColumnNames[0]))) {
|
|
320
336
|
// Composite FK has exactly one column which is not used in any other FK.
|
|
321
337
|
// The FK also doesn't have a mix of nullable and non-nullable columns,
|
|
322
338
|
// or its only nullable column is this very one.
|
|
@@ -391,14 +407,16 @@ export class DatabaseTable {
|
|
|
391
407
|
// But also does not skip if the column is not nullable, and yet all involved FKs are nullable,
|
|
392
408
|
// or if one or more FKs involved has multiple nullable columns.
|
|
393
409
|
smart: (column) => {
|
|
394
|
-
return columnsInFks.includes(column.name)
|
|
395
|
-
|
|
396
|
-
|
|
410
|
+
return (columnsInFks.includes(column.name) &&
|
|
411
|
+
!fksOnColumnProps.has(column.name) &&
|
|
412
|
+
(column.nullable
|
|
397
413
|
? columnFks[column.name].some(fk => !fk.columnNames.some(fkColumnName => fkColumnName !== column.name && this.getColumn(fkColumnName)?.nullable))
|
|
398
|
-
: columnFks[column.name].some(fk => !nullableForeignKeys.has(fk)));
|
|
414
|
+
: columnFks[column.name].some(fk => !nullableForeignKeys.has(fk))));
|
|
399
415
|
},
|
|
400
416
|
};
|
|
401
|
-
const skippedColumnNames = this.getColumns()
|
|
417
|
+
const skippedColumnNames = this.getColumns()
|
|
418
|
+
.filter(skippingHandlers[scalarPropertiesForRelations])
|
|
419
|
+
.map(column => column.name);
|
|
402
420
|
// Check standalone FKs named after columns for potential conflicts among themselves.
|
|
403
421
|
// This typically happens when two standalone FKs named after a column resolve to the same prop name
|
|
404
422
|
// because the respective columns include the referenced table in the name.
|
|
@@ -439,7 +457,8 @@ export class DatabaseTable {
|
|
|
439
457
|
findFkIndex(currentFk) {
|
|
440
458
|
const fkColumnsLength = currentFk.columnNames.length;
|
|
441
459
|
const possibleIndexes = this.indexes.filter(index => {
|
|
442
|
-
return index.columnNames.length === fkColumnsLength &&
|
|
460
|
+
return (index.columnNames.length === fkColumnsLength &&
|
|
461
|
+
!currentFk.columnNames.some((columnName, i) => index.columnNames[i] !== columnName));
|
|
443
462
|
});
|
|
444
463
|
possibleIndexes.sort((a, b) => {
|
|
445
464
|
if (a.primary !== b.primary) {
|
|
@@ -502,7 +521,8 @@ export class DatabaseTable {
|
|
|
502
521
|
return Array.from(propBaseNames).map(baseName => this.getPropertyName(namingStrategy, baseName, fksOnColumnProps.get(baseName)));
|
|
503
522
|
}
|
|
504
523
|
getSafeBaseNameForFkProp(namingStrategy, currentFk, fks, columnName) {
|
|
505
|
-
if (columnName &&
|
|
524
|
+
if (columnName &&
|
|
525
|
+
this.getPropertyName(namingStrategy, columnName, currentFk) !== this.getPropertyName(namingStrategy, columnName)) {
|
|
506
526
|
// The eligible scalar column name is different from the name of the FK prop of the same column.
|
|
507
527
|
// Both can be safely rendered.
|
|
508
528
|
// Use the column name as a base for the FK prop.
|
|
@@ -538,7 +558,9 @@ export class DatabaseTable {
|
|
|
538
558
|
*/
|
|
539
559
|
getShortestName(skipDefaultSchema = true) {
|
|
540
560
|
const defaultSchema = this.platform.getDefaultSchemaName();
|
|
541
|
-
if (!this.schema ||
|
|
561
|
+
if (!this.schema ||
|
|
562
|
+
this.name.startsWith(defaultSchema + '.') ||
|
|
563
|
+
(this.schema === defaultSchema && skipDefaultSchema)) {
|
|
542
564
|
return this.name;
|
|
543
565
|
}
|
|
544
566
|
return `${this.schema}.${this.name}`;
|
|
@@ -569,7 +591,7 @@ export class DatabaseTable {
|
|
|
569
591
|
}
|
|
570
592
|
getForeignKeyDeclaration(fk, namingStrategy, schemaHelper, fkIndex, nullable, propNameBase, fksOnColumnProps) {
|
|
571
593
|
const prop = this.getPropertyName(namingStrategy, propNameBase, fk);
|
|
572
|
-
const kind =
|
|
594
|
+
const kind = fkIndex?.unique && !fkIndex.primary ? this.getReferenceKind(fk, fkIndex) : this.getReferenceKind(fk);
|
|
573
595
|
const runtimeType = this.getPropertyTypeForForeignKey(namingStrategy, fk);
|
|
574
596
|
const fkOptions = {};
|
|
575
597
|
fkOptions.fieldNames = fk.columnNames;
|
|
@@ -584,8 +606,8 @@ export class DatabaseTable {
|
|
|
584
606
|
const column = this.getColumn(fk.columnNames[0]);
|
|
585
607
|
const defaultRaw = this.getPropertyDefaultValue(schemaHelper, column, column.type, true);
|
|
586
608
|
const defaultTs = this.getPropertyDefaultValue(schemaHelper, column, column.type);
|
|
587
|
-
columnOptions.default =
|
|
588
|
-
columnOptions.defaultRaw =
|
|
609
|
+
columnOptions.default = defaultRaw !== defaultTs || defaultRaw === '' ? defaultTs : undefined;
|
|
610
|
+
columnOptions.defaultRaw = column.nullable && defaultRaw === 'null' ? undefined : defaultRaw;
|
|
589
611
|
columnOptions.optional = typeof column.generated !== 'undefined' || defaultRaw !== 'null';
|
|
590
612
|
columnOptions.generated = column.generated;
|
|
591
613
|
columnOptions.nullable = column.nullable;
|
|
@@ -607,25 +629,29 @@ export class DatabaseTable {
|
|
|
607
629
|
nullable,
|
|
608
630
|
primary: fkIndex?.primary || !fk.columnNames.some(columnName => !this.getPrimaryKey()?.columnNames.includes(columnName)),
|
|
609
631
|
index: !fkIndex?.unique ? fkIndex?.keyName : undefined,
|
|
610
|
-
unique:
|
|
632
|
+
unique: fkIndex?.unique && !fkIndex.primary ? fkIndex.keyName : undefined,
|
|
611
633
|
...fkOptions,
|
|
612
634
|
};
|
|
613
635
|
}
|
|
614
636
|
getPropertyDeclaration(column, namingStrategy, schemaHelper, compositeFkIndexes, compositeFkUniques, columnFks, fk) {
|
|
615
637
|
const prop = this.getPropertyName(namingStrategy, column.name, fk);
|
|
616
638
|
const persist = !(column.name in columnFks && typeof fk === 'undefined');
|
|
617
|
-
const index = compositeFkIndexes[prop] ||
|
|
618
|
-
|
|
639
|
+
const index = compositeFkIndexes[prop] ||
|
|
640
|
+
this.indexes.find(idx => idx.columnNames[0] === column.name && !idx.composite && !idx.unique && !idx.primary);
|
|
641
|
+
const unique = compositeFkUniques[prop] ||
|
|
642
|
+
this.indexes.find(idx => idx.columnNames[0] === column.name && !idx.composite && idx.unique && !idx.primary);
|
|
619
643
|
const kind = this.getReferenceKind(fk, unique);
|
|
620
644
|
const runtimeType = this.getPropertyTypeForColumn(namingStrategy, column, fk);
|
|
621
|
-
const type = fk
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
645
|
+
const type = fk
|
|
646
|
+
? runtimeType
|
|
647
|
+
: (Utils.keys(t).find(k => {
|
|
648
|
+
const typeInCoreMap = this.platform.getMappedType(k);
|
|
649
|
+
return ((typeInCoreMap !== Type.getType(UnknownType) || k === 'unknown') && typeInCoreMap === column.mappedType);
|
|
650
|
+
}) ?? runtimeType);
|
|
651
|
+
const ignoreSchemaChanges = type === 'unknown' && column.length ? (column.extra ? ['type', 'extra'] : ['type']) : undefined;
|
|
626
652
|
const defaultRaw = this.getPropertyDefaultValue(schemaHelper, column, runtimeType, true);
|
|
627
653
|
const defaultParsed = this.getPropertyDefaultValue(schemaHelper, column, runtimeType);
|
|
628
|
-
const defaultTs =
|
|
654
|
+
const defaultTs = defaultRaw !== defaultParsed || defaultParsed === '' ? defaultParsed : undefined;
|
|
629
655
|
const fkOptions = {};
|
|
630
656
|
if (fk) {
|
|
631
657
|
fkOptions.fieldNames = fk.columnNames;
|
|
@@ -646,7 +672,7 @@ export class DatabaseTable {
|
|
|
646
672
|
optional: defaultRaw !== 'null' || defaultTs != null || typeof column.generated !== 'undefined',
|
|
647
673
|
columnType: column.type,
|
|
648
674
|
default: defaultTs,
|
|
649
|
-
defaultRaw:
|
|
675
|
+
defaultRaw: column.nullable && defaultRaw === 'null' ? undefined : defaultRaw,
|
|
650
676
|
nullable: column.nullable,
|
|
651
677
|
primary: column.primary && persist,
|
|
652
678
|
autoincrement: column.autoincrement,
|
|
@@ -720,7 +746,7 @@ export class DatabaseTable {
|
|
|
720
746
|
const defaultValue = column.default ?? 'null';
|
|
721
747
|
const val = schemaHelper.normalizeDefaultValue(defaultValue, column.length);
|
|
722
748
|
if (val === 'null') {
|
|
723
|
-
return raw ? 'null' :
|
|
749
|
+
return raw ? 'null' : column.nullable ? null : undefined;
|
|
724
750
|
}
|
|
725
751
|
if (propType === 'boolean' && !raw) {
|
|
726
752
|
return !['0', 'false', 'f', 'n', 'no', 'off'].includes('' + column.default);
|
|
@@ -752,7 +778,9 @@ export class DatabaseTable {
|
|
|
752
778
|
}
|
|
753
779
|
addIndex(meta, index, type) {
|
|
754
780
|
// If columns are specified but properties are not, derive properties from column names
|
|
755
|
-
if (index.columns?.length &&
|
|
781
|
+
if (index.columns?.length &&
|
|
782
|
+
!index.expression &&
|
|
783
|
+
(!index.properties || Utils.asArray(index.properties).length === 0)) {
|
|
756
784
|
index = { ...index, properties: index.columns.map(c => c.name) };
|
|
757
785
|
}
|
|
758
786
|
const properties = Utils.unique(Utils.flatten(Utils.asArray(index.properties).map(prop => {
|
|
@@ -787,13 +815,15 @@ export class DatabaseTable {
|
|
|
787
815
|
}
|
|
788
816
|
const name = this.getIndexName(index.name, properties, type);
|
|
789
817
|
// Process include columns (map property names to field names)
|
|
790
|
-
const includeColumns = index.include
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
818
|
+
const includeColumns = index.include
|
|
819
|
+
? Utils.unique(Utils.flatten(Utils.asArray(index.include).map(prop => {
|
|
820
|
+
if (meta.properties[prop]) {
|
|
821
|
+
return meta.properties[prop].fieldNames;
|
|
822
|
+
}
|
|
823
|
+
/* v8 ignore next */
|
|
824
|
+
return [prop];
|
|
825
|
+
})))
|
|
826
|
+
: undefined;
|
|
797
827
|
// Process columns with advanced options (map property names to field names)
|
|
798
828
|
const columns = index.columns?.map(col => {
|
|
799
829
|
const fieldName = meta.properties[col.name]?.fieldNames[0] ?? col.name;
|
|
@@ -59,7 +59,9 @@ export class SchemaComparator {
|
|
|
59
59
|
if (toSchema.hasNativeEnum(key)) {
|
|
60
60
|
continue;
|
|
61
61
|
}
|
|
62
|
-
if (key.startsWith(`${fromSchema.name}.`) &&
|
|
62
|
+
if (key.startsWith(`${fromSchema.name}.`) &&
|
|
63
|
+
(fromSchema.name !== toSchema.name ||
|
|
64
|
+
toSchema.getNativeEnum(key.substring(fromSchema.name.length + 1))?.schema === '*')) {
|
|
63
65
|
continue;
|
|
64
66
|
}
|
|
65
67
|
diff.removedNativeEnums.push(nativeEnum);
|
|
@@ -164,7 +166,10 @@ export class SchemaComparator {
|
|
|
164
166
|
};
|
|
165
167
|
if (this.diffComment(fromTable.comment, toTable.comment)) {
|
|
166
168
|
tableDifferences.changedComment = toTable.comment;
|
|
167
|
-
this.log(`table comment changed for ${tableDifferences.name}`, {
|
|
169
|
+
this.log(`table comment changed for ${tableDifferences.name}`, {
|
|
170
|
+
fromTableComment: fromTable.comment,
|
|
171
|
+
toTableComment: toTable.comment,
|
|
172
|
+
});
|
|
168
173
|
changes++;
|
|
169
174
|
}
|
|
170
175
|
const fromTableColumns = fromTable.getColumns();
|
|
@@ -222,7 +227,7 @@ export class SchemaComparator {
|
|
|
222
227
|
// See if there are any removed indexes in "to" table
|
|
223
228
|
for (const index of fromTableIndexes) {
|
|
224
229
|
// See if index is removed in "to" table.
|
|
225
|
-
if ((index.primary && !toTable.hasPrimaryKey()) || !index.primary && !toTable.hasIndex(index.keyName)) {
|
|
230
|
+
if ((index.primary && !toTable.hasPrimaryKey()) || (!index.primary && !toTable.hasIndex(index.keyName))) {
|
|
226
231
|
tableDifferences.removedIndexes[index.keyName] = index;
|
|
227
232
|
this.log(`index ${index.keyName} removed from table ${tableDifferences.name}`);
|
|
228
233
|
changes++;
|
|
@@ -234,7 +239,10 @@ export class SchemaComparator {
|
|
|
234
239
|
continue;
|
|
235
240
|
}
|
|
236
241
|
tableDifferences.changedIndexes[index.keyName] = toTableIndex;
|
|
237
|
-
this.log(`index ${index.keyName} changed in table ${tableDifferences.name}`, {
|
|
242
|
+
this.log(`index ${index.keyName} changed in table ${tableDifferences.name}`, {
|
|
243
|
+
fromTableIndex: index,
|
|
244
|
+
toTableIndex,
|
|
245
|
+
});
|
|
238
246
|
changes++;
|
|
239
247
|
}
|
|
240
248
|
this.detectIndexRenamings(tableDifferences);
|
|
@@ -264,10 +272,15 @@ export class SchemaComparator {
|
|
|
264
272
|
if (!this.diffExpression(check.expression, toTableCheck.expression)) {
|
|
265
273
|
continue;
|
|
266
274
|
}
|
|
267
|
-
if (fromColumn?.enumItems &&
|
|
275
|
+
if (fromColumn?.enumItems &&
|
|
276
|
+
toColumn?.enumItems &&
|
|
277
|
+
!this.diffEnumItems(fromColumn.enumItems, toColumn.enumItems)) {
|
|
268
278
|
continue;
|
|
269
279
|
}
|
|
270
|
-
this.log(`check constraint ${check.name} changed in table ${tableDifferences.name}`, {
|
|
280
|
+
this.log(`check constraint ${check.name} changed in table ${tableDifferences.name}`, {
|
|
281
|
+
fromTableCheck: check,
|
|
282
|
+
toTableCheck,
|
|
283
|
+
});
|
|
271
284
|
tableDifferences.changedChecks[check.name] = toTableCheck;
|
|
272
285
|
changes++;
|
|
273
286
|
}
|
|
@@ -280,7 +293,10 @@ export class SchemaComparator {
|
|
|
280
293
|
delete toForeignKeys[toConstraint.constraintName];
|
|
281
294
|
}
|
|
282
295
|
else if (fromConstraint.constraintName.toLowerCase() === toConstraint.constraintName.toLowerCase()) {
|
|
283
|
-
this.log(`FK constraint ${fromConstraint.constraintName} changed in table ${tableDifferences.name}`, {
|
|
296
|
+
this.log(`FK constraint ${fromConstraint.constraintName} changed in table ${tableDifferences.name}`, {
|
|
297
|
+
fromConstraint,
|
|
298
|
+
toConstraint,
|
|
299
|
+
});
|
|
284
300
|
tableDifferences.changedForeignKeys[toConstraint.constraintName] = toConstraint;
|
|
285
301
|
changes++;
|
|
286
302
|
delete fromForeignKeys[fromConstraint.constraintName];
|
|
@@ -295,7 +311,9 @@ export class SchemaComparator {
|
|
|
295
311
|
}
|
|
296
312
|
for (const toConstraint of Object.values(toForeignKeys)) {
|
|
297
313
|
tableDifferences.addedForeignKeys[toConstraint.constraintName] = toConstraint;
|
|
298
|
-
this.log(`FK constraint ${toConstraint.constraintName} added to table ${tableDifferences.name}`, {
|
|
314
|
+
this.log(`FK constraint ${toConstraint.constraintName} added to table ${tableDifferences.name}`, {
|
|
315
|
+
constraint: toConstraint,
|
|
316
|
+
});
|
|
299
317
|
changes++;
|
|
300
318
|
}
|
|
301
319
|
return changes ? tableDifferences : false;
|
|
@@ -341,7 +359,10 @@ export class SchemaComparator {
|
|
|
341
359
|
tableDifferences.renamedColumns[removedColumnName] = addedColumn;
|
|
342
360
|
delete tableDifferences.addedColumns[addedColumnName];
|
|
343
361
|
delete tableDifferences.removedColumns[removedColumnName];
|
|
344
|
-
this.log(`renamed column detected in table ${tableDifferences.name}`, {
|
|
362
|
+
this.log(`renamed column detected in table ${tableDifferences.name}`, {
|
|
363
|
+
old: removedColumnName,
|
|
364
|
+
new: addedColumnName,
|
|
365
|
+
});
|
|
345
366
|
}
|
|
346
367
|
}
|
|
347
368
|
/**
|
|
@@ -375,7 +396,10 @@ export class SchemaComparator {
|
|
|
375
396
|
tableDifferences.renamedIndexes[removedIndexName] = addedIndex;
|
|
376
397
|
delete tableDifferences.addedIndexes[addedIndexName];
|
|
377
398
|
delete tableDifferences.removedIndexes[removedIndexName];
|
|
378
|
-
this.log(`renamed index detected in table ${tableDifferences.name}`, {
|
|
399
|
+
this.log(`renamed index detected in table ${tableDifferences.name}`, {
|
|
400
|
+
old: removedIndexName,
|
|
401
|
+
new: addedIndexName,
|
|
402
|
+
});
|
|
379
403
|
}
|
|
380
404
|
}
|
|
381
405
|
diffForeignKey(key1, key2, tableDifferences) {
|
|
@@ -402,10 +426,7 @@ export class SchemaComparator {
|
|
|
402
426
|
}
|
|
403
427
|
const defaultRule = ['restrict', 'no action'];
|
|
404
428
|
const rule = (key, method) => {
|
|
405
|
-
return (key[method] ?? defaultRule[0])
|
|
406
|
-
.toLowerCase()
|
|
407
|
-
.replace(defaultRule[1], defaultRule[0])
|
|
408
|
-
.replace(/"/g, '');
|
|
429
|
+
return (key[method] ?? defaultRule[0]).toLowerCase().replace(defaultRule[1], defaultRule[0]).replace(/"/g, '');
|
|
409
430
|
};
|
|
410
431
|
const compare = (method) => rule(key1, method) === rule(key2, method);
|
|
411
432
|
return !compare('updateRule') || !compare('deleteRule');
|
|
@@ -418,7 +439,8 @@ export class SchemaComparator {
|
|
|
418
439
|
const fromProp = this.mapColumnToProperty({ ...fromColumn, autoincrement: false });
|
|
419
440
|
const toProp = this.mapColumnToProperty({ ...toColumn, autoincrement: false });
|
|
420
441
|
const fromColumnType = this.platform.normalizeColumnType(fromColumn.mappedType.getColumnType(fromProp, this.platform).toLowerCase(), fromProp);
|
|
421
|
-
const fromNativeEnum = fromTable.nativeEnums[fromColumnType] ??
|
|
442
|
+
const fromNativeEnum = fromTable.nativeEnums[fromColumnType] ??
|
|
443
|
+
Object.values(fromTable.nativeEnums).find(e => e.name === fromColumnType && e.schema !== '*');
|
|
422
444
|
let toColumnType = this.platform.normalizeColumnType(toColumn.mappedType.getColumnType(toProp, this.platform).toLowerCase(), toProp);
|
|
423
445
|
const log = (msg, params) => {
|
|
424
446
|
if (logging) {
|
|
@@ -430,8 +452,11 @@ export class SchemaComparator {
|
|
|
430
452
|
if (fromColumnType !== toColumnType &&
|
|
431
453
|
(!fromNativeEnum || `${fromNativeEnum.schema}.${fromNativeEnum.name}` !== toColumnType) &&
|
|
432
454
|
!(fromColumn.ignoreSchemaChanges?.includes('type') || toColumn.ignoreSchemaChanges?.includes('type')) &&
|
|
433
|
-
!fromColumn.generated &&
|
|
434
|
-
|
|
455
|
+
!fromColumn.generated &&
|
|
456
|
+
!toColumn.generated) {
|
|
457
|
+
if (!toColumnType.includes('.') &&
|
|
458
|
+
fromTable.schema &&
|
|
459
|
+
fromTable.schema !== this.platform.getDefaultSchemaName()) {
|
|
435
460
|
toColumnType = `${fromTable.schema}.${toColumnType}`;
|
|
436
461
|
}
|
|
437
462
|
if (fromColumnType !== toColumnType) {
|
|
@@ -455,8 +480,8 @@ export class SchemaComparator {
|
|
|
455
480
|
log(`'unsigned' changed for column ${fromTable.name}.${fromColumn.name}`, { fromColumn, toColumn });
|
|
456
481
|
changedProperties.add('unsigned');
|
|
457
482
|
}
|
|
458
|
-
if (!(fromColumn.ignoreSchemaChanges?.includes('default') ||
|
|
459
|
-
|
|
483
|
+
if (!(fromColumn.ignoreSchemaChanges?.includes('default') || toColumn.ignoreSchemaChanges?.includes('default')) &&
|
|
484
|
+
!this.hasSameDefaultValue(fromColumn, toColumn)) {
|
|
460
485
|
log(`'default' changed for column ${fromTable.name}.${fromColumn.name}`, { fromColumn, toColumn });
|
|
461
486
|
changedProperties.add('default');
|
|
462
487
|
}
|
|
@@ -471,8 +496,7 @@ export class SchemaComparator {
|
|
|
471
496
|
changedProperties.add('enumItems');
|
|
472
497
|
}
|
|
473
498
|
if ((fromColumn.extra || '').toLowerCase() !== (toColumn.extra || '').toLowerCase() &&
|
|
474
|
-
!(fromColumn.ignoreSchemaChanges?.includes('extra') ||
|
|
475
|
-
toColumn.ignoreSchemaChanges?.includes('extra'))) {
|
|
499
|
+
!(fromColumn.ignoreSchemaChanges?.includes('extra') || toColumn.ignoreSchemaChanges?.includes('extra'))) {
|
|
476
500
|
log(`'extra' changed for column ${fromTable.name}.${fromColumn.name}`, { fromColumn, toColumn });
|
|
477
501
|
changedProperties.add('extra');
|
|
478
502
|
}
|
|
@@ -579,7 +603,7 @@ export class SchemaComparator {
|
|
|
579
603
|
if (sort1 !== sort2) {
|
|
580
604
|
return false;
|
|
581
605
|
}
|
|
582
|
-
const defaultNulls = (s) => s === 'DESC' ? 'FIRST' : 'LAST';
|
|
606
|
+
const defaultNulls = (s) => (s === 'DESC' ? 'FIRST' : 'LAST');
|
|
583
607
|
const nulls1 = c1.nulls?.toUpperCase() ?? defaultNulls(sort1);
|
|
584
608
|
const nulls2 = c2.nulls?.toUpperCase() ?? defaultNulls(sort2);
|
|
585
609
|
if (nulls1 !== nulls2) {
|
|
@@ -610,9 +634,9 @@ export class SchemaComparator {
|
|
|
610
634
|
// expressions like check constraints might be normalized by the driver,
|
|
611
635
|
// e.g. quotes might be added (https://github.com/mikro-orm/mikro-orm/issues/3827)
|
|
612
636
|
const simplify = (str) => {
|
|
613
|
-
return str
|
|
637
|
+
return (str
|
|
614
638
|
?.replace(/_\w+'(.*?)'/g, '$1')
|
|
615
|
-
.replace(/in\s*\((.*?)\)/
|
|
639
|
+
.replace(/in\s*\((.*?)\)/gi, '= any (array[$1])')
|
|
616
640
|
// MySQL normalizes count(*) to count(0)
|
|
617
641
|
.replace(/\bcount\s*\(\s*0\s*\)/gi, 'count(*)')
|
|
618
642
|
// Remove quotes first so we can process identifiers
|
|
@@ -628,7 +652,7 @@ export class SchemaComparator {
|
|
|
628
652
|
.replace(/\bas\b/gi, '')
|
|
629
653
|
// Remove remaining special chars, parentheses, type casts, asterisks, and normalize whitespace
|
|
630
654
|
.replace(/[()\n[\]*]|::\w+| +/g, '')
|
|
631
|
-
.replace(/anyarray\[(.*)]/
|
|
655
|
+
.replace(/anyarray\[(.*)]/gi, '$1')
|
|
632
656
|
.toLowerCase()
|
|
633
657
|
// PostgreSQL adds default aliases to aggregate functions (e.g., count(*) AS count)
|
|
634
658
|
// After removing AS and whitespace, this results in duplicate adjacent words
|
|
@@ -636,7 +660,7 @@ export class SchemaComparator {
|
|
|
636
660
|
// Use lookahead to match repeated patterns of 3+ chars (avoid false positives on short sequences)
|
|
637
661
|
.replace(/(\w{3,})\1/g, '$1')
|
|
638
662
|
// Remove trailing semicolon (PostgreSQL adds it to view definitions)
|
|
639
|
-
.replace(/;$/, '');
|
|
663
|
+
.replace(/;$/, ''));
|
|
640
664
|
};
|
|
641
665
|
return simplify(expr1) !== simplify(expr2);
|
|
642
666
|
}
|
|
@@ -645,13 +669,13 @@ export class SchemaComparator {
|
|
|
645
669
|
if (!defaultValue) {
|
|
646
670
|
return null;
|
|
647
671
|
}
|
|
648
|
-
const val = defaultValue
|
|
649
|
-
.replace(/^(_\w+\\)?'(.*?)\\?'$/, '$2')
|
|
650
|
-
.replace(/^\(?'(.*?)'\)?$/, '$1');
|
|
672
|
+
const val = defaultValue.replace(/^(_\w+\\)?'(.*?)\\?'$/, '$2').replace(/^\(?'(.*?)'\)?$/, '$1');
|
|
651
673
|
return parseJsonSafe(val);
|
|
652
674
|
}
|
|
653
675
|
hasSameDefaultValue(from, to) {
|
|
654
|
-
if (from.default == null ||
|
|
676
|
+
if (from.default == null ||
|
|
677
|
+
from.default.toString().toLowerCase() === 'null' ||
|
|
678
|
+
from.default.toString().startsWith('nextval(')) {
|
|
655
679
|
return to.default == null || to.default.toLowerCase() === 'null';
|
|
656
680
|
}
|
|
657
681
|
if (to.mappedType instanceof BooleanType) {
|
package/schema/SchemaHelper.js
CHANGED
|
@@ -78,7 +78,7 @@ export class SchemaHelper {
|
|
|
78
78
|
tableName = this.quote(tableName);
|
|
79
79
|
oldColumnName = this.quote(oldColumnName);
|
|
80
80
|
const columnName = this.quote(to.name);
|
|
81
|
-
const schemaReference =
|
|
81
|
+
const schemaReference = schemaName !== undefined && schemaName !== 'public' ? '"' + schemaName + '".' : '';
|
|
82
82
|
const tableReference = schemaReference + tableName;
|
|
83
83
|
return `alter table ${tableReference} rename column ${oldColumnName} to ${columnName}`;
|
|
84
84
|
}
|
|
@@ -124,7 +124,8 @@ export class SchemaHelper {
|
|
|
124
124
|
*/
|
|
125
125
|
getIndexColumns(index) {
|
|
126
126
|
if (index.columns?.length) {
|
|
127
|
-
return index.columns
|
|
127
|
+
return index.columns
|
|
128
|
+
.map(col => {
|
|
128
129
|
let colDef = this.quote(col.name);
|
|
129
130
|
// Collation comes after column name (SQLite syntax: column COLLATE name)
|
|
130
131
|
if (col.collation) {
|
|
@@ -139,7 +140,8 @@ export class SchemaHelper {
|
|
|
139
140
|
colDef += ` nulls ${col.nulls}`;
|
|
140
141
|
}
|
|
141
142
|
return colDef;
|
|
142
|
-
})
|
|
143
|
+
})
|
|
144
|
+
.join(', ');
|
|
143
145
|
}
|
|
144
146
|
return index.columnNames.map(c => this.quote(c)).join(', ');
|
|
145
147
|
}
|
|
@@ -256,9 +258,11 @@ export class SchemaHelper {
|
|
|
256
258
|
return ret;
|
|
257
259
|
}
|
|
258
260
|
getAddColumnsSQL(table, columns) {
|
|
259
|
-
const adds = columns
|
|
261
|
+
const adds = columns
|
|
262
|
+
.map(column => {
|
|
260
263
|
return `add ${this.createTableColumn(column, table)}`;
|
|
261
|
-
})
|
|
264
|
+
})
|
|
265
|
+
.join(', ');
|
|
262
266
|
return [`alter table ${table.getQuotedName()} ${adds}`];
|
|
263
267
|
}
|
|
264
268
|
getDropColumnsSQL(tableName, columns, schemaName) {
|
|
@@ -310,12 +314,26 @@ export class SchemaHelper {
|
|
|
310
314
|
Utils.runIfNotEmpty(() => col.push('not null'), !column.nullable && !column.generated);
|
|
311
315
|
Utils.runIfNotEmpty(() => col.push('auto_increment'), column.autoincrement);
|
|
312
316
|
Utils.runIfNotEmpty(() => col.push('unique'), column.autoincrement && !column.primary);
|
|
313
|
-
if (column.autoincrement &&
|
|
317
|
+
if (column.autoincrement &&
|
|
318
|
+
!column.generated &&
|
|
319
|
+
!compositePK &&
|
|
320
|
+
(!changedProperties || changedProperties.has('autoincrement') || changedProperties.has('type'))) {
|
|
314
321
|
Utils.runIfNotEmpty(() => col.push('primary key'), primaryKey && column.primary);
|
|
315
322
|
}
|
|
316
323
|
if (useDefault) {
|
|
317
324
|
// https://dev.mysql.com/doc/refman/9.0/en/data-type-defaults.html
|
|
318
|
-
const needsExpression = [
|
|
325
|
+
const needsExpression = [
|
|
326
|
+
'blob',
|
|
327
|
+
'text',
|
|
328
|
+
'json',
|
|
329
|
+
'point',
|
|
330
|
+
'linestring',
|
|
331
|
+
'polygon',
|
|
332
|
+
'multipoint',
|
|
333
|
+
'multilinestring',
|
|
334
|
+
'multipolygon',
|
|
335
|
+
'geometrycollection',
|
|
336
|
+
].some(type => column.type.toLowerCase().startsWith(type));
|
|
319
337
|
const defaultSql = needsExpression && !column.default.startsWith('(') ? `(${column.default})` : column.default;
|
|
320
338
|
col.push(`default ${defaultSql}`);
|
|
321
339
|
}
|
|
@@ -371,7 +389,9 @@ export class SchemaHelper {
|
|
|
371
389
|
columnNames: [fk.column_name],
|
|
372
390
|
constraintName: fk.constraint_name,
|
|
373
391
|
localTableName: schemaName ? `${schemaName}.${tableName}` : tableName,
|
|
374
|
-
referencedTableName: fk.referenced_schema_name
|
|
392
|
+
referencedTableName: fk.referenced_schema_name
|
|
393
|
+
? `${fk.referenced_schema_name}.${fk.referenced_table_name}`
|
|
394
|
+
: fk.referenced_table_name,
|
|
375
395
|
referencedColumnNames: [fk.referenced_column_name],
|
|
376
396
|
updateRule: fk.update_rule.toLowerCase(),
|
|
377
397
|
deleteRule: fk.delete_rule.toLowerCase(),
|
|
@@ -128,7 +128,8 @@ export class SqlSchemaGenerator extends AbstractSchemaGenerator {
|
|
|
128
128
|
}
|
|
129
129
|
const schema = options?.schema ?? this.config.get('schema', this.platform.getDefaultSchemaName());
|
|
130
130
|
for (const meta of this.getOrderedMetadata(schema).reverse()) {
|
|
131
|
-
await this.driver
|
|
131
|
+
await this.driver
|
|
132
|
+
.createQueryBuilder(meta.class, this.em?.getTransactionContext(), 'write', false)
|
|
132
133
|
.withSchema(schema)
|
|
133
134
|
.truncate()
|
|
134
135
|
.execute();
|
|
@@ -224,8 +225,11 @@ export class SqlSchemaGenerator extends AbstractSchemaGenerator {
|
|
|
224
225
|
options.dropTables ??= true;
|
|
225
226
|
const toSchema = this.getTargetSchema(options.schema);
|
|
226
227
|
const schemas = toSchema.getNamespaces();
|
|
227
|
-
const fromSchema = options.fromSchema ??
|
|
228
|
-
|
|
228
|
+
const fromSchema = options.fromSchema ??
|
|
229
|
+
(await DatabaseSchema.create(this.connection, this.platform, this.config, options.schema, schemas, undefined, this.options.skipTables, this.options.skipViews));
|
|
230
|
+
const wildcardSchemaTables = [...this.metadata.getAll().values()]
|
|
231
|
+
.filter(meta => meta.schema === '*')
|
|
232
|
+
.map(meta => meta.tableName);
|
|
229
233
|
fromSchema.prune(options.schema, wildcardSchemaTables);
|
|
230
234
|
toSchema.prune(options.schema, wildcardSchemaTables);
|
|
231
235
|
return { fromSchema, toSchema };
|
|
@@ -408,7 +412,11 @@ export class SqlSchemaGenerator extends AbstractSchemaGenerator {
|
|
|
408
412
|
return;
|
|
409
413
|
}
|
|
410
414
|
const statements = groups.flatMap(group => {
|
|
411
|
-
return group
|
|
415
|
+
return group
|
|
416
|
+
.join('\n')
|
|
417
|
+
.split(';\n')
|
|
418
|
+
.map(s => s.trim())
|
|
419
|
+
.filter(s => s);
|
|
412
420
|
});
|
|
413
421
|
await Utils.runSerial(statements, stmt => this.driver.execute(stmt));
|
|
414
422
|
}
|
|
@@ -476,9 +484,7 @@ export class SqlSchemaGenerator extends AbstractSchemaGenerator {
|
|
|
476
484
|
}
|
|
477
485
|
// Check if the definition references the other view's name
|
|
478
486
|
// Use word boundary matching to avoid false positives
|
|
479
|
-
const patterns = [
|
|
480
|
-
new RegExp(`\\b${this.escapeRegExp(otherView.name.toLowerCase())}\\b`),
|
|
481
|
-
];
|
|
487
|
+
const patterns = [new RegExp(`\\b${this.escapeRegExp(otherView.name.toLowerCase())}\\b`)];
|
|
482
488
|
if (otherView.schema) {
|
|
483
489
|
patterns.push(new RegExp(`\\b${this.escapeRegExp(`${otherView.schema}.${otherView.name}`.toLowerCase())}\\b`));
|
|
484
490
|
}
|