@mikro-orm/sql 7.3.0-dev.10 → 7.3.0-dev.12
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/dialects/mysql/BaseMySqlPlatform.d.ts +1 -0
- package/dialects/mysql/BaseMySqlPlatform.js +3 -0
- package/dialects/mysql/MySqlSchemaHelper.d.ts +2 -0
- package/dialects/mysql/MySqlSchemaHelper.js +13 -0
- package/dialects/postgresql/PostgreSqlSchemaHelper.d.ts +2 -0
- package/dialects/postgresql/PostgreSqlSchemaHelper.js +78 -22
- package/dialects/sqlite/SqlitePlatform.d.ts +1 -0
- package/dialects/sqlite/SqlitePlatform.js +3 -0
- package/dialects/sqlite/SqliteSchemaHelper.d.ts +1 -0
- package/dialects/sqlite/SqliteSchemaHelper.js +13 -0
- package/package.json +3 -3
- package/query/QueryBuilderHelper.js +18 -9
- package/schema/DatabaseTable.js +9 -2
- package/schema/SchemaComparator.d.ts +1 -1
- package/schema/SchemaComparator.js +13 -4
- package/schema/SchemaHelper.d.ts +7 -1
- package/schema/SchemaHelper.js +32 -0
- package/schema/SqlSchemaGenerator.d.ts +5 -2
- package/schema/SqlSchemaGenerator.js +26 -7
- package/schema/partitioning.js +6 -6
- package/typings.d.ts +3 -1
|
@@ -37,6 +37,7 @@ export declare class BaseMySqlPlatform extends AbstractSqlPlatform {
|
|
|
37
37
|
getDefaultMappedType(type: string): Type<unknown>;
|
|
38
38
|
isNumericColumn(mappedType: Type<unknown>): boolean;
|
|
39
39
|
supportsUnsigned(): boolean;
|
|
40
|
+
supportsAutoincrementIncrement(): boolean;
|
|
40
41
|
/** MySQL/MariaDB identifier limit. */
|
|
41
42
|
getMaxIdentifierLength(): number;
|
|
42
43
|
getIndexName(tableName: string, columns: string[], type: 'index' | 'unique' | 'foreign' | 'primary' | 'sequence' | 'check'): string;
|
|
@@ -20,6 +20,8 @@ export declare class MySqlSchemaHelper extends SchemaHelper {
|
|
|
20
20
|
tableExists(connection: AbstractSqlConnection, tableName: string, schemaName: string | undefined, ctx?: Transaction): Promise<boolean>;
|
|
21
21
|
disableForeignKeysSQL(): string;
|
|
22
22
|
enableForeignKeysSQL(): string;
|
|
23
|
+
getResetSequenceSQL(tableName: string, schemaName: string | undefined, columnName: string, startWith: number): string;
|
|
24
|
+
getAddColumnsSQL(table: DatabaseTable, columns: Column[]): string[];
|
|
23
25
|
finalizeTable(table: DatabaseTable, charset: string, collate?: string): string;
|
|
24
26
|
getListTablesSQL(): string;
|
|
25
27
|
getListViewsSQL(): string;
|
|
@@ -42,12 +42,25 @@ export class MySqlSchemaHelper extends SchemaHelper {
|
|
|
42
42
|
enableForeignKeysSQL() {
|
|
43
43
|
return 'set foreign_key_checks = 1;';
|
|
44
44
|
}
|
|
45
|
+
getResetSequenceSQL(tableName, schemaName, columnName, startWith) {
|
|
46
|
+
return `alter table ${this.quote(this.getTableName(tableName, schemaName))} auto_increment = ${startWith}`;
|
|
47
|
+
}
|
|
48
|
+
getAddColumnsSQL(table, columns) {
|
|
49
|
+
const [sql, ...rest] = super.getAddColumnsSQL(table, columns);
|
|
50
|
+
const startWith = columns.find(column => column.autoincrement)?.sequence?.startWith;
|
|
51
|
+
// the table option numbers the existing rows of the added column from the declared start too
|
|
52
|
+
return [startWith != null ? `${sql}, auto_increment = ${startWith}` : sql, ...rest];
|
|
53
|
+
}
|
|
45
54
|
finalizeTable(table, charset, collate) {
|
|
46
55
|
let sql = ` default character set ${charset}`;
|
|
47
56
|
if (collate) {
|
|
48
57
|
sql += ` collate ${collate}`;
|
|
49
58
|
}
|
|
50
59
|
sql += ' engine = InnoDB';
|
|
60
|
+
const startWith = table.getColumns().find(column => column.autoincrement)?.sequence?.startWith;
|
|
61
|
+
if (startWith != null) {
|
|
62
|
+
sql += ` auto_increment = ${startWith}`;
|
|
63
|
+
}
|
|
51
64
|
if (table.comment) {
|
|
52
65
|
sql += ` comment = ${this.platform.quoteValue(table.comment)}`;
|
|
53
66
|
}
|
|
@@ -29,6 +29,7 @@ export declare class PostgreSqlSchemaHelper extends SchemaHelper {
|
|
|
29
29
|
loadMaterializedViews(schema: DatabaseSchema, connection: AbstractSqlConnection, schemaName?: string, ctx?: Transaction): Promise<void>;
|
|
30
30
|
createMaterializedView(name: string, schema: string | undefined, definition: string, withData?: boolean): string;
|
|
31
31
|
createTable(table: DatabaseTable, alter?: boolean): string[];
|
|
32
|
+
getResetSequenceSQL(tableName: string, schemaName: string | undefined, columnName: string, startWith: number): string;
|
|
32
33
|
dropMaterializedViewIfExists(name: string, schema?: string): string;
|
|
33
34
|
refreshMaterializedView(name: string, schema?: string, concurrently?: boolean): string;
|
|
34
35
|
getNamespaces(connection: AbstractSqlConnection, ctx?: Transaction): Promise<string[]>;
|
|
@@ -134,6 +135,7 @@ export declare class PostgreSqlSchemaHelper extends SchemaHelper {
|
|
|
134
135
|
castColumn(name: string, type: string): string;
|
|
135
136
|
dropForeignKey(tableName: string, constraintName: string): string;
|
|
136
137
|
getPostAlterTable(tableDiff: TableDifference, safe: boolean): string[];
|
|
138
|
+
getAddColumnsSQL(table: DatabaseTable, columns: Column[]): string[];
|
|
137
139
|
private getAlterColumnAutoincrement;
|
|
138
140
|
getChangeColumnCommentSQL(tableName: string, to: Column, schemaName?: string): string;
|
|
139
141
|
alterTableComment(table: DatabaseTable, comment?: string): string;
|
|
@@ -5,6 +5,8 @@ import { normalizePartitionBound, normalizePartitionDefinition } from '../../sch
|
|
|
5
5
|
const POSTGIS_VIEWS = ['geography_columns', 'geometry_columns'];
|
|
6
6
|
/** Dollar-quote delimiter, e.g. `$$` or `$body$`, captured so `split` keeps it. */
|
|
7
7
|
const DOLLAR_QUOTE_TAG = /(\$(?:[A-Za-z_]\w*)?\$)/;
|
|
8
|
+
/** pg-added `(<operand>)::type` cast in a check body, including multi word types like `double precision`. */
|
|
9
|
+
const CHECK_CAST_RE = /\(([^()]*)\)::\w+(?: precision| varying| with(?:out)? time zone)?(?:\[\])?/g;
|
|
8
10
|
export class PostgreSqlSchemaHelper extends SchemaHelper {
|
|
9
11
|
static DEFAULT_VALUES = {
|
|
10
12
|
'now()': ['now()', 'current_timestamp'],
|
|
@@ -106,10 +108,23 @@ export class PostgreSqlSchemaHelper extends SchemaHelper {
|
|
|
106
108
|
}
|
|
107
109
|
createTable(table, alter) {
|
|
108
110
|
const partitioning = table.getPartitioning();
|
|
111
|
+
const [createTable, ...rest] = super.createTable(table, alter);
|
|
112
|
+
// `serial` columns can't declare sequence options inline, so alter the implicit sequence right after creating the table
|
|
113
|
+
const sequences = table
|
|
114
|
+
.getColumns()
|
|
115
|
+
.filter(column => column.autoincrement && !column.generated && this.getSequenceOptionsSQL(column))
|
|
116
|
+
.map(column => {
|
|
117
|
+
const sequence = `pg_get_serial_sequence(${this.platform.quoteValue(table.getQuotedName())}, ${this.platform.quoteValue(column.name)})`;
|
|
118
|
+
const { startWith = -1, incrementBy = 1 } = column.sequence;
|
|
119
|
+
// `alter sequence` keeps the ascending bounds of the serial, so a descending one gets the bounds and start of a fresh sequence
|
|
120
|
+
const options = incrementBy < 0
|
|
121
|
+
? `no minvalue maxvalue ${Math.max(startWith, -1)} start with ${startWith} increment by ${incrementBy}`
|
|
122
|
+
: this.getSequenceOptionsSQL(column);
|
|
123
|
+
return `do $$ begin execute format('alter sequence %s ${options} restart', ${sequence}); end $$`;
|
|
124
|
+
});
|
|
109
125
|
if (!partitioning) {
|
|
110
|
-
return
|
|
126
|
+
return [createTable, ...sequences, ...rest];
|
|
111
127
|
}
|
|
112
|
-
const [createTable, ...rest] = super.createTable(table, alter);
|
|
113
128
|
const partitions = partitioning.partitions.map(partition => {
|
|
114
129
|
const partitionName = this.quote(this.getTableName(partition.name, partition.schema ?? table.schema));
|
|
115
130
|
return `create table ${partitionName} partition of ${table.getQuotedName()} ${partition.bound}`;
|
|
@@ -119,7 +134,12 @@ export class PostgreSqlSchemaHelper extends SchemaHelper {
|
|
|
119
134
|
// regex replacement tokens like `$$`, `$&`, or `$1` inside user-supplied expressions (e.g., a
|
|
120
135
|
// callback that returns a dollar-quoted literal) are not interpreted as back-references.
|
|
121
136
|
const spliced = `${createTable.slice(0, -1)} partition by ${partitioning.definition};`;
|
|
122
|
-
return [spliced, ...rest, ...partitions];
|
|
137
|
+
return [spliced, ...sequences, ...rest, ...partitions];
|
|
138
|
+
}
|
|
139
|
+
getResetSequenceSQL(tableName, schemaName, columnName, startWith) {
|
|
140
|
+
const table = this.platform.quoteValue(this.quote(this.getTableName(tableName, schemaName)));
|
|
141
|
+
// `truncate … restart identity` goes back to the start the sequence was created with, which the metadata may not match
|
|
142
|
+
return `select setval(pg_get_serial_sequence(${table}, ${this.platform.quoteValue(columnName)}), ${startWith}, false)`;
|
|
123
143
|
}
|
|
124
144
|
dropMaterializedViewIfExists(name, schema) {
|
|
125
145
|
return `drop materialized view if exists ${this.quote(this.getTableName(name, schema))} cascade`;
|
|
@@ -387,15 +407,16 @@ export class PostgreSqlSchemaHelper extends SchemaHelper {
|
|
|
387
407
|
data_type,
|
|
388
408
|
is_identity,
|
|
389
409
|
identity_generation,
|
|
390
|
-
identity_start,
|
|
391
|
-
identity_increment,
|
|
392
410
|
generation_expression,
|
|
393
411
|
pg_catalog.col_description(pgc.oid, cols.ordinal_position::int) column_comment,
|
|
394
|
-
coll.collname as collation_name
|
|
412
|
+
coll.collname as collation_name,
|
|
413
|
+
seq.seqstart as sequence_start,
|
|
414
|
+
seq.seqincrement as sequence_increment
|
|
395
415
|
from information_schema.columns cols
|
|
396
416
|
join pg_class pgc on cols.table_name = pgc.relname
|
|
397
417
|
join pg_attribute pga on pgc.oid = pga.attrelid and cols.column_name = pga.attname
|
|
398
418
|
left join pg_collation coll on pga.attcollation = coll.oid and coll.collname <> 'default'
|
|
419
|
+
left join pg_sequence seq on seq.seqrelid = pg_get_serial_sequence(quote_ident(cols.table_schema) || '.' || quote_ident(cols.table_name), cols.column_name)::regclass
|
|
399
420
|
where (${[...tablesBySchemas.entries()].map(([schema, tables]) => `(table_schema = ${this.platform.quoteValue(schema)} and table_name in (${tables.map(t => this.platform.quoteValue(t.table_name)).join(',')}))`).join(' or ')})
|
|
400
421
|
order by ordinal_position`;
|
|
401
422
|
const allColumns = await connection.execute(sql, [], 'all', ctx);
|
|
@@ -435,14 +456,6 @@ export class PostgreSqlSchemaHelper extends SchemaHelper {
|
|
|
435
456
|
type += `(${col.numeric_precision},${col.numeric_scale})`;
|
|
436
457
|
}
|
|
437
458
|
const length = this.inferLengthFromColumnType(type) === -1 ? -1 : col.length;
|
|
438
|
-
// a descending sequence starts at -1 by default
|
|
439
|
-
const defaultIdentityStart = col.identity_increment?.startsWith('-') ? '-1' : '1';
|
|
440
|
-
// read back non-default identity options, so `identity (start with 1000)` does not diff against plain `identity`
|
|
441
|
-
const identityOptions = [
|
|
442
|
-
col.identity_start !== defaultIdentityStart && `start with ${col.identity_start}`,
|
|
443
|
-
col.identity_increment !== '1' && `increment by ${col.identity_increment}`,
|
|
444
|
-
].filter(Boolean);
|
|
445
|
-
const identitySuffix = identityOptions.length > 0 ? ` (${identityOptions.join(' ')})` : '';
|
|
446
459
|
const column = {
|
|
447
460
|
name: col.column_name,
|
|
448
461
|
type,
|
|
@@ -454,8 +467,11 @@ export class PostgreSqlSchemaHelper extends SchemaHelper {
|
|
|
454
467
|
default: str(this.normalizeDefaultValue(col.column_default, col.length)),
|
|
455
468
|
unsigned: increments,
|
|
456
469
|
autoincrement: increments,
|
|
470
|
+
sequence: increments ? this.getIntrospectedSequence(col.sequence_start, col.sequence_increment) : undefined,
|
|
457
471
|
generated: col.is_identity === 'YES'
|
|
458
|
-
?
|
|
472
|
+
? col.identity_generation === 'BY DEFAULT'
|
|
473
|
+
? 'by default as identity'
|
|
474
|
+
: 'identity'
|
|
459
475
|
: col.generation_expression
|
|
460
476
|
? col.generation_expression + ' stored'
|
|
461
477
|
: undefined,
|
|
@@ -513,7 +529,7 @@ export class PostgreSqlSchemaHelper extends SchemaHelper {
|
|
|
513
529
|
// SchemaHelper.createCheck).
|
|
514
530
|
const m = /^check \(\((.*)\)\)$/is.exec(check.expression);
|
|
515
531
|
const single = m ? null : /^check \((.*)\)$/is.exec(check.expression);
|
|
516
|
-
const def = m ? m[1].replace(
|
|
532
|
+
const def = m ? m[1].replace(CHECK_CAST_RE, '$1') : single ? single[1] : check.expression;
|
|
517
533
|
ret[key].push({
|
|
518
534
|
name: check.name,
|
|
519
535
|
columnName: check.column_name,
|
|
@@ -1040,11 +1056,15 @@ export class PostgreSqlSchemaHelper extends SchemaHelper {
|
|
|
1040
1056
|
columnType = this.quote(columnType);
|
|
1041
1057
|
}
|
|
1042
1058
|
}
|
|
1043
|
-
|
|
1044
|
-
|
|
1059
|
+
const sequenceOptions = this.getSequenceOptionsSQL(column);
|
|
1060
|
+
const generated = sequenceOptions && /^(by default as )?identity$/.test(column.generated ?? '')
|
|
1061
|
+
? `${column.generated} (${sequenceOptions})`
|
|
1062
|
+
: column.generated;
|
|
1063
|
+
if (generated?.startsWith('by default as identity')) {
|
|
1064
|
+
columnType += ` generated ${generated}`;
|
|
1045
1065
|
}
|
|
1046
|
-
else if (
|
|
1047
|
-
columnType += ` generated always as ${
|
|
1066
|
+
else if (generated) {
|
|
1067
|
+
columnType += ` generated always as ${generated}`;
|
|
1048
1068
|
}
|
|
1049
1069
|
col.push(columnType);
|
|
1050
1070
|
Utils.runIfNotEmpty(() => col.push(this.getCollateSQL(column.collation)), column.collation);
|
|
@@ -1201,14 +1221,50 @@ export class PostgreSqlSchemaHelper extends SchemaHelper {
|
|
|
1201
1221
|
}
|
|
1202
1222
|
return ret;
|
|
1203
1223
|
}
|
|
1224
|
+
getAddColumnsSQL(table, columns) {
|
|
1225
|
+
const compositePK = table.getPrimaryKey()?.composite;
|
|
1226
|
+
const before = [];
|
|
1227
|
+
const after = [];
|
|
1228
|
+
const adds = columns.map(column => {
|
|
1229
|
+
const sequenceOptions = this.getSequenceOptionsSQL(column);
|
|
1230
|
+
if (!sequenceOptions || !column.autoincrement || column.generated) {
|
|
1231
|
+
return column;
|
|
1232
|
+
}
|
|
1233
|
+
// a `serial` column numbers the existing rows right away, so expand it to a sequence created with the options upfront
|
|
1234
|
+
const seqName = this.quote(this.getTableName(this.platform.getIndexName(table.name, [column.name], 'sequence'), table.schema));
|
|
1235
|
+
before.push(`create sequence ${seqName} ${sequenceOptions}`);
|
|
1236
|
+
after.push(`alter sequence ${seqName} owned by ${table.getQuotedName()}.${this.quote(column.name)}`);
|
|
1237
|
+
// the expanded column has no inline `primary key`, which `alterTable` expects from an added autoincrement PK
|
|
1238
|
+
if (column.primary && !compositePK && !this.hasNonDefaultPrimaryKeyName(table)) {
|
|
1239
|
+
after.push(`alter table ${table.getQuotedName()} add primary key (${this.quote(column.name)})`);
|
|
1240
|
+
}
|
|
1241
|
+
return {
|
|
1242
|
+
...column,
|
|
1243
|
+
type: column.mappedType.getColumnType({ autoincrement: false }, this.platform),
|
|
1244
|
+
autoincrement: false,
|
|
1245
|
+
nullable: false,
|
|
1246
|
+
default: `nextval('${seqName}')`,
|
|
1247
|
+
};
|
|
1248
|
+
});
|
|
1249
|
+
return [...before, ...super.getAddColumnsSQL(table, adds), ...after];
|
|
1250
|
+
}
|
|
1204
1251
|
getAlterColumnAutoincrement(tableName, column, schemaName) {
|
|
1205
1252
|
const ret = [];
|
|
1206
1253
|
/* v8 ignore next */
|
|
1207
1254
|
const name = (schemaName && schemaName !== this.platform.getDefaultSchemaName() ? schemaName + '.' : '') + tableName;
|
|
1208
1255
|
if (column.autoincrement) {
|
|
1209
1256
|
const seqName = this.platform.getIndexName(tableName, [column.name], 'sequence');
|
|
1210
|
-
|
|
1211
|
-
|
|
1257
|
+
const sequenceOptions = this.getSequenceOptionsSQL(column);
|
|
1258
|
+
const { incrementBy = 1, startWith = incrementBy < 0 ? -1 : 1 } = column.sequence ?? {};
|
|
1259
|
+
const lastValue = `(select ${incrementBy < 0 ? 'min' : 'max'}(${this.quote(column.name)}) from ${this.quote(name)})`;
|
|
1260
|
+
ret.push(`create sequence if not exists ${this.quote(seqName)}${sequenceOptions ? ` ${sequenceOptions}` : ''}`);
|
|
1261
|
+
if (sequenceOptions) {
|
|
1262
|
+
// continue after the existing values, but never before the declared start
|
|
1263
|
+
ret.push(`select setval('${seqName}', ${incrementBy < 0 ? 'least' : 'greatest'}(${lastValue} + ${incrementBy}, ${startWith}), false)`);
|
|
1264
|
+
}
|
|
1265
|
+
else {
|
|
1266
|
+
ret.push(`select setval('${seqName}', ${lastValue})`);
|
|
1267
|
+
}
|
|
1212
1268
|
ret.push(`alter table ${this.quote(name)} alter column ${this.quote(column.name)} set default nextval('${seqName}')`);
|
|
1213
1269
|
}
|
|
1214
1270
|
return ret;
|
|
@@ -14,6 +14,7 @@ export declare class SqlitePlatform extends AbstractSqlPlatform {
|
|
|
14
14
|
usesReturningStatement(): boolean;
|
|
15
15
|
usesEnumCheckConstraints(): boolean;
|
|
16
16
|
supportsComments(): boolean;
|
|
17
|
+
supportsAutoincrementIncrement(): boolean;
|
|
17
18
|
getCurrentTimestampSQL(length: number): string;
|
|
18
19
|
getDateTimeTypeDeclarationSQL(column: {
|
|
19
20
|
length: number;
|
|
@@ -21,6 +21,7 @@ export declare class SqliteSchemaHelper extends SchemaHelper {
|
|
|
21
21
|
getDropDatabaseSQL(name: string): string;
|
|
22
22
|
loadInformationSchema(schema: DatabaseSchema, connection: AbstractSqlConnection, tables: Table[], schemas?: string[], ctx?: Transaction): Promise<void>;
|
|
23
23
|
createTable(table: DatabaseTable, alter?: boolean): string[];
|
|
24
|
+
getResetSequenceSQL(tableName: string, schemaName: string | undefined, columnName: string, startWith: number): string;
|
|
24
25
|
createTableColumn(column: Column, table: DatabaseTable, _changedProperties?: Set<string>): string | undefined;
|
|
25
26
|
getAddColumnsSQL(table: DatabaseTable, columns: Column[], diff?: TableDifference): string[];
|
|
26
27
|
dropForeignKey(tableName: string, constraintName: string): string;
|
|
@@ -144,6 +144,13 @@ export class SqliteSchemaHelper extends SchemaHelper {
|
|
|
144
144
|
}
|
|
145
145
|
const ret = [];
|
|
146
146
|
this.append(ret, sql);
|
|
147
|
+
const startWith = columns.find(column => column.autoincrement)?.sequence?.startWith;
|
|
148
|
+
if (startWith != null) {
|
|
149
|
+
// a table rebuild renames the counter row of the temp table over, so only seed it when missing
|
|
150
|
+
const sequenceTable = this.quote(this.getTableName('sqlite_sequence', table.schema));
|
|
151
|
+
const name = this.platform.quoteValue(table.name);
|
|
152
|
+
this.append(ret, `insert into ${sequenceTable} (name, seq) select ${name}, ${startWith - 1} where not exists (select 1 from ${sequenceTable} where name = ${name})`);
|
|
153
|
+
}
|
|
147
154
|
for (const index of table.getIndexes()) {
|
|
148
155
|
this.append(ret, this.createIndex(index, table));
|
|
149
156
|
}
|
|
@@ -152,6 +159,12 @@ export class SqliteSchemaHelper extends SchemaHelper {
|
|
|
152
159
|
}
|
|
153
160
|
return ret;
|
|
154
161
|
}
|
|
162
|
+
getResetSequenceSQL(tableName, schemaName, columnName, startWith) {
|
|
163
|
+
const sequenceTable = this.quote(this.getTableName('sqlite_sequence', schemaName));
|
|
164
|
+
const name = this.platform.quoteValue(tableName);
|
|
165
|
+
// the counter row does not exist before the first insert when the table was created without `sequence`
|
|
166
|
+
return `delete from ${sequenceTable} where name = ${name};\ninsert into ${sequenceTable} (name, seq) values (${name}, ${startWith - 1})`;
|
|
167
|
+
}
|
|
155
168
|
createTableColumn(column, table, _changedProperties) {
|
|
156
169
|
const col = [this.quote(column.name)];
|
|
157
170
|
const checks = table.getChecks();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mikro-orm/sql",
|
|
3
|
-
"version": "7.3.0-dev.
|
|
3
|
+
"version": "7.3.0-dev.12",
|
|
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
|
"keywords": [
|
|
6
6
|
"data-mapper",
|
|
@@ -50,10 +50,10 @@
|
|
|
50
50
|
"kysely": "0.29.6"
|
|
51
51
|
},
|
|
52
52
|
"devDependencies": {
|
|
53
|
-
"@mikro-orm/core": "^7.2.
|
|
53
|
+
"@mikro-orm/core": "^7.2.3"
|
|
54
54
|
},
|
|
55
55
|
"peerDependencies": {
|
|
56
|
-
"@mikro-orm/core": "7.3.0-dev.
|
|
56
|
+
"@mikro-orm/core": "7.3.0-dev.12"
|
|
57
57
|
},
|
|
58
58
|
"engines": {
|
|
59
59
|
"node": ">= 22.17.0"
|
|
@@ -2,6 +2,16 @@ import { ALIAS_REPLACEMENT, ALIAS_REPLACEMENT_RE, ArrayType, JsonType, inspect,
|
|
|
2
2
|
import { EMBEDDABLE_ARRAY_OPS, JoinType, QueryType } from './enums.js';
|
|
3
3
|
/** Captures the column name that follows the alias placeholder. */
|
|
4
4
|
const QUALIFYING_ALIAS_RE = new RegExp(ALIAS_REPLACEMENT_RE + '(?=["\'`\\]]*\\.["\'`\\[]?([\\w$]+))', 'g');
|
|
5
|
+
/** Maps regexp tokens to LIKE pattern tokens, escaping LIKE wildcards with `!`. */
|
|
6
|
+
const LIKE_REPLACEMENTS = {
|
|
7
|
+
'\\.': '.',
|
|
8
|
+
'\\/': '/',
|
|
9
|
+
'.*': '%',
|
|
10
|
+
'.': '_',
|
|
11
|
+
'!': '!!',
|
|
12
|
+
'%': '!%',
|
|
13
|
+
_: '!_',
|
|
14
|
+
};
|
|
5
15
|
/**
|
|
6
16
|
* @internal
|
|
7
17
|
*/
|
|
@@ -359,19 +369,17 @@ export class QueryBuilderHelper {
|
|
|
359
369
|
if (!(re instanceof RegExp)) {
|
|
360
370
|
return false;
|
|
361
371
|
}
|
|
362
|
-
if (re.flags
|
|
372
|
+
if (/[im]/.test(re.flags)) {
|
|
363
373
|
return false;
|
|
364
374
|
}
|
|
365
|
-
//
|
|
366
|
-
return
|
|
375
|
+
// LIKE can only express edge anchors, `.`, `.*` and escaped `.` or `/`, anything else needs the regexp operator
|
|
376
|
+
return /^\^?(?:[^\\^$.*+?|{}[\]()]|\\[./]|\.\*?)*\$?$/.test(re.source);
|
|
367
377
|
}
|
|
368
378
|
getRegExpParam(re) {
|
|
369
379
|
const value = re.source
|
|
370
|
-
.replace(
|
|
371
|
-
.replace(
|
|
372
|
-
.replace(/\\_/g,
|
|
373
|
-
.replace(/^\^/g, '') // remove ^ from start
|
|
374
|
-
.replace(/\$$/g, ''); // remove $ from end
|
|
380
|
+
.replace(/^\^/, '') // remove ^ from start
|
|
381
|
+
.replace(/\$$/, '') // remove $ from end
|
|
382
|
+
.replace(/\\[./]|\.\*|[.!%_]/g, m => LIKE_REPLACEMENTS[m]);
|
|
375
383
|
if (re.source.startsWith('^') && re.source.endsWith('$')) {
|
|
376
384
|
return value;
|
|
377
385
|
}
|
|
@@ -445,7 +453,8 @@ export class QueryBuilderHelper {
|
|
|
445
453
|
const parts = [];
|
|
446
454
|
const params = [];
|
|
447
455
|
if (this.isSimpleRegExp(cond[key])) {
|
|
448
|
-
|
|
456
|
+
const escape = /[!%_]/.test(cond[key].source) ? ` escape '!'` : '';
|
|
457
|
+
parts.push(`${this.#platform.quoteIdentifier(this.mapper(key, type))} like ?${escape}`);
|
|
449
458
|
params.push(this.getRegExpParam(cond[key]));
|
|
450
459
|
return { sql: parts.join(' and '), params };
|
|
451
460
|
}
|
package/schema/DatabaseTable.js
CHANGED
|
@@ -155,6 +155,7 @@ export class DatabaseTable {
|
|
|
155
155
|
comment: prop.comment,
|
|
156
156
|
collation: prop.collation,
|
|
157
157
|
extra: prop.extra,
|
|
158
|
+
sequence: prop.sequence,
|
|
158
159
|
ignoreSchemaChanges: prop.ignoreSchemaChanges,
|
|
159
160
|
};
|
|
160
161
|
this.#columns[field].unsigned ??= this.#columns[field].autoincrement;
|
|
@@ -796,6 +797,7 @@ export class DatabaseTable {
|
|
|
796
797
|
nullable: column.nullable,
|
|
797
798
|
primary: column.primary && persist,
|
|
798
799
|
autoincrement: column.autoincrement,
|
|
800
|
+
sequence: column.sequence,
|
|
799
801
|
fieldName: column.name,
|
|
800
802
|
unsigned: column.unsigned,
|
|
801
803
|
length: column.length,
|
|
@@ -975,17 +977,22 @@ export class DatabaseTable {
|
|
|
975
977
|
throw new Error(`Index '${name}' on entity '${meta.className}': cannot combine \`expression\` with \`where\` — inline the WHERE clause into the \`expression\` escape hatch, or drop \`expression\` and use structured \`properties\` + \`where\`.`);
|
|
976
978
|
}
|
|
977
979
|
const where = this.processIndexWhere(index.where, meta);
|
|
980
|
+
const expression = this.processIndexExpression(name, index.expression, meta);
|
|
978
981
|
this.#indexes.push({
|
|
979
982
|
keyName: name,
|
|
980
983
|
columnNames: properties,
|
|
981
984
|
composite: properties.length > 1,
|
|
982
985
|
// JSON columns can have unique index but not unique constraint, and we need to distinguish those, so we can properly drop them.
|
|
983
986
|
// Partial indexes (`where`) must use CREATE [UNIQUE] INDEX form — constraints can't carry predicates.
|
|
984
|
-
|
|
987
|
+
// Raw `create [unique] index` expressions are plain indexes, not constraints.
|
|
988
|
+
constraint: type !== 'index' &&
|
|
989
|
+
!properties.some((d) => d.includes('.')) &&
|
|
990
|
+
!where &&
|
|
991
|
+
!/^\s*create\s/i.test(expression ?? ''),
|
|
985
992
|
primary: type === 'primary',
|
|
986
993
|
unique: type !== 'index',
|
|
987
994
|
type: index.type,
|
|
988
|
-
expression
|
|
995
|
+
expression,
|
|
989
996
|
where,
|
|
990
997
|
options: index.options,
|
|
991
998
|
deferMode: index.deferMode,
|
|
@@ -30,7 +30,7 @@ export declare class SchemaComparator {
|
|
|
30
30
|
* Returns the difference between the tables fromTable and toTable.
|
|
31
31
|
* If there are no differences this method returns the boolean false.
|
|
32
32
|
*/
|
|
33
|
-
diffTable(fromTable: DatabaseTable, toTable: DatabaseTable, inverseTableDiff?: TableDifference): TableDifference | false;
|
|
33
|
+
diffTable(fromTable: DatabaseTable, toTable: DatabaseTable, inverseTableDiff?: TableDifference, inverse?: boolean): TableDifference | false;
|
|
34
34
|
/**
|
|
35
35
|
* Try to find columns that only changed their name, rename operations maybe cheaper than add/drop
|
|
36
36
|
* however ambiguities between different possibilities should not lead to renaming at all.
|
|
@@ -77,7 +77,7 @@ export class SchemaComparator {
|
|
|
77
77
|
diff.newTables[tableName] = toSchema.getTable(tableName);
|
|
78
78
|
}
|
|
79
79
|
else {
|
|
80
|
-
const tableDifferences = this.diffTable(fromSchema.getTable(tableName), toSchema.getTable(tableName), inverseDiff?.changedTables[tableName]);
|
|
80
|
+
const tableDifferences = this.diffTable(fromSchema.getTable(tableName), toSchema.getTable(tableName), inverseDiff?.changedTables[tableName], !!inverseDiff);
|
|
81
81
|
if (tableDifferences !== false) {
|
|
82
82
|
diff.changedTables[tableName] = tableDifferences;
|
|
83
83
|
}
|
|
@@ -319,7 +319,7 @@ export class SchemaComparator {
|
|
|
319
319
|
* Returns the difference between the tables fromTable and toTable.
|
|
320
320
|
* If there are no differences this method returns the boolean false.
|
|
321
321
|
*/
|
|
322
|
-
diffTable(fromTable, toTable, inverseTableDiff) {
|
|
322
|
+
diffTable(fromTable, toTable, inverseTableDiff, inverse = false) {
|
|
323
323
|
let changes = 0;
|
|
324
324
|
const tableDifferences = {
|
|
325
325
|
name: fromTable.getShortestName(),
|
|
@@ -353,7 +353,9 @@ export class SchemaComparator {
|
|
|
353
353
|
});
|
|
354
354
|
changes++;
|
|
355
355
|
}
|
|
356
|
-
|
|
356
|
+
// partitioning is diffed one way (declared vs. existing), so the down diff has to compare it like the up one did
|
|
357
|
+
const [existingTable, declaredTable] = inverse ? [toTable, fromTable] : [fromTable, toTable];
|
|
358
|
+
if (diffPartitioning(existingTable.getPartitioning(), declaredTable.getPartitioning(), this.#platform.getDefaultSchemaName())) {
|
|
357
359
|
tableDifferences.changedPartitioning = {
|
|
358
360
|
from: fromTable.getPartitioning(),
|
|
359
361
|
to: toTable.getPartitioning(),
|
|
@@ -477,8 +479,10 @@ export class SchemaComparator {
|
|
|
477
479
|
if (!this.diffExpression(check.expression, toTableCheck.expression)) {
|
|
478
480
|
continue;
|
|
479
481
|
}
|
|
482
|
+
// only auto-generated enum checks, a user check tagged with an enum column must be diffed by expression
|
|
480
483
|
if (fromColumn?.enumItems &&
|
|
481
484
|
toColumn?.enumItems &&
|
|
485
|
+
check.name === this.#platform.getIndexName(fromTable.name, [fromColumn.name], 'check') &&
|
|
482
486
|
!this.diffEnumItems(fromColumn.enumItems, toColumn.enumItems)) {
|
|
483
487
|
continue;
|
|
484
488
|
}
|
|
@@ -708,7 +712,9 @@ export class SchemaComparator {
|
|
|
708
712
|
log(`'nullable' changed for column ${fromTable.name}.${fromColumn.name}`, { fromColumn, toColumn });
|
|
709
713
|
changedProperties.add('nullable');
|
|
710
714
|
}
|
|
711
|
-
|
|
715
|
+
// identity options (start/increment) are applied on create only, same as the `sequence` option
|
|
716
|
+
const withoutIdentityOptions = (generated) => generated?.replace(/^((?:by default as )?identity)\s*\(.*\)$/is, '$1');
|
|
717
|
+
if (this.diffExpression(withoutIdentityOptions(fromColumn.generated), withoutIdentityOptions(toColumn.generated))) {
|
|
712
718
|
log(`'generated' changed for column ${fromTable.name}.${fromColumn.name}`, { fromColumn, toColumn });
|
|
713
719
|
changedProperties.add('generated');
|
|
714
720
|
}
|
|
@@ -907,6 +913,7 @@ export class SchemaComparator {
|
|
|
907
913
|
?.replace(/(?<![\w'])_\w+'(.*?)'/g, '$1')
|
|
908
914
|
.replace(/!=/g, '<>')
|
|
909
915
|
// `\b` keeps this from firing inside identifiers like `min(...)`
|
|
916
|
+
.replace(/\bnot\s+in\s*\((.*?)\)/gi, '<> all (array[$1])')
|
|
910
917
|
.replace(/\bin\s*\((.*?)\)/gi, '= any (array[$1])')
|
|
911
918
|
// MySQL normalizes count(*) to count(0)
|
|
912
919
|
.replace(/\bcount\s*\(\s*0\s*\)/gi, 'count(*)')
|
|
@@ -922,6 +929,8 @@ export class SchemaComparator {
|
|
|
922
929
|
// MySQL adds table/alias prefixes to columns (e.g., a.name or table_name.column vs just column)
|
|
923
930
|
// Strip these prefixes - match word.word patterns and keep only the last part
|
|
924
931
|
.replace(/\b\w+\.(\w+)/g, '$1')
|
|
932
|
+
// PostgreSQL rewrites `x [not] between a and b` to `x >= a and x <= b` (`x < a or x > b`), cast targets are skipped
|
|
933
|
+
.replace(/(?<!::\s*)\b(?!not\b)(\w+)\s+(not\s+)?between\s+(\S+)\s+and\s+(\S+)/gi, (_, x, not, a, b) => not ? `${x} < ${a} or ${x} > ${b}` : `${x} >= ${a} and ${x} <= ${b}`)
|
|
925
934
|
// Normalize JOIN syntax: inner join -> join (equivalent in SQL)
|
|
926
935
|
.replace(/\binner\s+join\b/gi, 'join')
|
|
927
936
|
// PostgreSQL names an unaliased bare function call after the function itself,
|
package/schema/SchemaHelper.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type Connection, type Dictionary, type Options, type Transaction, type RawQueryFragment } from '@mikro-orm/core';
|
|
1
|
+
import { type Connection, type Dictionary, type Options, type Transaction, type RawQueryFragment, type SequenceOptions } from '@mikro-orm/core';
|
|
2
2
|
import type { AbstractSqlConnection } from '../AbstractSqlConnection.js';
|
|
3
3
|
import type { AbstractSqlPlatform } from '../AbstractSqlPlatform.js';
|
|
4
4
|
import type { CheckDef, Column, ForeignKey, IndexDef, Table, TableDifference, SqlTriggerDef, SqlRoutineDef } from '../typings.js';
|
|
@@ -45,6 +45,12 @@ export declare abstract class SchemaHelper {
|
|
|
45
45
|
resolveMigrationSchema(schema: string | undefined): string | undefined;
|
|
46
46
|
finalizeTable(table: DatabaseTable, charset: string, collate?: string): string;
|
|
47
47
|
appendComments(table: DatabaseTable): string[];
|
|
48
|
+
/** Returns SQL that moves the autoincrement counter back to `sequence.startWith` after `clear()` truncated the table. */
|
|
49
|
+
getResetSequenceSQL(tableName: string, schemaName: string | undefined, columnName: string, startWith: number): string;
|
|
50
|
+
/** Maps the introspected start and increment of an autoincrement column to its `sequence` option, omitting defaults. */
|
|
51
|
+
protected getIntrospectedSequence(start: unknown, increment: unknown): SequenceOptions | undefined;
|
|
52
|
+
/** Renders the `start with … increment by …` clause for the `sequence` option of an autoincrement column. */
|
|
53
|
+
protected getSequenceOptionsSQL(column: Column): string;
|
|
48
54
|
supportsSchemaConstraints(): boolean;
|
|
49
55
|
getPrimaryKeys(connection: AbstractSqlConnection, indexes: IndexDef[] | undefined, tableName: string, schemaName?: string): Promise<string[]>;
|
|
50
56
|
inferLengthFromColumnType(type: string): number | undefined;
|
package/schema/SchemaHelper.js
CHANGED
|
@@ -91,6 +91,38 @@ export class SchemaHelper {
|
|
|
91
91
|
appendComments(table) {
|
|
92
92
|
return [];
|
|
93
93
|
}
|
|
94
|
+
/** Returns SQL that moves the autoincrement counter back to `sequence.startWith` after `clear()` truncated the table. */
|
|
95
|
+
/* v8 ignore next */
|
|
96
|
+
getResetSequenceSQL(tableName, schemaName, columnName, startWith) {
|
|
97
|
+
return '';
|
|
98
|
+
}
|
|
99
|
+
/** Maps the introspected start and increment of an autoincrement column to its `sequence` option, omitting defaults. */
|
|
100
|
+
getIntrospectedSequence(start, increment) {
|
|
101
|
+
if (start == null) {
|
|
102
|
+
return undefined;
|
|
103
|
+
}
|
|
104
|
+
const incrementBy = Number(increment ?? 1);
|
|
105
|
+
const startWith = Number(start);
|
|
106
|
+
const sequence = {};
|
|
107
|
+
if (startWith !== (incrementBy < 0 ? -1 : 1)) {
|
|
108
|
+
sequence.startWith = startWith;
|
|
109
|
+
}
|
|
110
|
+
if (incrementBy !== 1) {
|
|
111
|
+
sequence.incrementBy = incrementBy;
|
|
112
|
+
}
|
|
113
|
+
return Utils.hasObjectKeys(sequence) ? sequence : undefined;
|
|
114
|
+
}
|
|
115
|
+
/** Renders the `start with … increment by …` clause for the `sequence` option of an autoincrement column. */
|
|
116
|
+
getSequenceOptionsSQL(column) {
|
|
117
|
+
const { startWith, incrementBy } = column.sequence ?? {};
|
|
118
|
+
const descending = incrementBy != null && incrementBy < 0;
|
|
119
|
+
// a sequence is bounded by 1 (ascending) or -1 (descending) by default, a start beyond that has to move the bound
|
|
120
|
+
const bound = startWith != null &&
|
|
121
|
+
(descending ? startWith > -1 && `maxvalue ${startWith}` : startWith < 1 && `minvalue ${startWith}`);
|
|
122
|
+
return [startWith != null && `start with ${startWith}`, incrementBy != null && `increment by ${incrementBy}`, bound]
|
|
123
|
+
.filter(Boolean)
|
|
124
|
+
.join(' ');
|
|
125
|
+
}
|
|
94
126
|
supportsSchemaConstraints() {
|
|
95
127
|
return true;
|
|
96
128
|
}
|
|
@@ -38,10 +38,13 @@ export declare class SqlSchemaGenerator extends AbstractSchemaGenerator<Abstract
|
|
|
38
38
|
schema?: string;
|
|
39
39
|
}): string;
|
|
40
40
|
/**
|
|
41
|
-
*
|
|
41
|
+
* Collects foreign keys from other tables that reference `table` (self-references are excluded — those travel with the rebuild).
|
|
42
|
+
* Foreign keys the referencing table drops or changes in the same diff are left to its own alter, and those of dropped tables are skipped.
|
|
42
43
|
*/
|
|
43
|
-
/** Collects foreign keys from other tables that reference `table` (self-references are excluded — those travel with the rebuild). */
|
|
44
44
|
private getInboundForeignKeys;
|
|
45
|
+
/**
|
|
46
|
+
* We need to drop foreign keys first for all tables to allow dropping PK constraints.
|
|
47
|
+
*/
|
|
45
48
|
private preAlterTable;
|
|
46
49
|
/**
|
|
47
50
|
* creates new database and connects to it
|
|
@@ -141,6 +141,14 @@ export class SqlSchemaGenerator extends AbstractSchemaGenerator {
|
|
|
141
141
|
.withSchema(schema)
|
|
142
142
|
.truncate()
|
|
143
143
|
.execute();
|
|
144
|
+
// the counters of a TPT child's inherited columns (PK included) belong to the parent table
|
|
145
|
+
const props = meta.tptParent ? (meta.ownProps ?? []) : meta.props;
|
|
146
|
+
for (const prop of props.filter(prop => prop.autoincrement && prop.sequence?.startWith != null)) {
|
|
147
|
+
const sql = this.helper.getResetSequenceSQL(meta.tableName, this.getSchemaName(meta, { schema }), prop.fieldNames[0], prop.sequence.startWith);
|
|
148
|
+
if (sql) {
|
|
149
|
+
await this.execute(sql, { ctx: this.em?.getTransactionContext() });
|
|
150
|
+
}
|
|
151
|
+
}
|
|
144
152
|
}
|
|
145
153
|
catch (e) {
|
|
146
154
|
if (this.platform.getExceptionConverter().convertException(e) instanceof TableNotFoundException) {
|
|
@@ -327,14 +335,16 @@ export class SqlSchemaGenerator extends AbstractSchemaGenerator {
|
|
|
327
335
|
// Tables whose partitioning was added/changed can't be altered in place — they are rebuilt as a unit
|
|
328
336
|
// (the helper handles the data-preserving swap), so keep them out of the regular alter passes.
|
|
329
337
|
const changedTables = Object.values(schemaDiff.changedTables);
|
|
338
|
+
const dropsTables = !!options.dropTables && !options.safe;
|
|
330
339
|
const partitioningRebuilds = changedTables
|
|
331
340
|
.filter(changedTable => changedTable.changedPartitioning)
|
|
332
|
-
.map(diff => ({ diff, inboundForeignKeys: this.getInboundForeignKeys(schemaDiff
|
|
341
|
+
.map(diff => ({ diff, inboundForeignKeys: this.getInboundForeignKeys(schemaDiff, diff.toTable, dropsTables) }));
|
|
333
342
|
const alteredTables = changedTables.filter(changedTable => !changedTable.changedPartitioning);
|
|
334
|
-
|
|
343
|
+
// before the rebuild, so inbound FKs the referencing table drops or changes are gone by then
|
|
335
344
|
for (const changedTable of alteredTables) {
|
|
336
345
|
this.append(ret, this.preAlterTable(changedTable, options.safe), true);
|
|
337
346
|
}
|
|
347
|
+
this.append(ret, this.helper.getPartitioningRebuildSQL(partitioningRebuilds, options.safe), true);
|
|
338
348
|
for (const changedTable of alteredTables) {
|
|
339
349
|
this.append(ret, this.helper.alterTable(changedTable, options.safe), true);
|
|
340
350
|
}
|
|
@@ -394,18 +404,24 @@ export class SqlSchemaGenerator extends AbstractSchemaGenerator {
|
|
|
394
404
|
return this.wrapSchema(ret, options);
|
|
395
405
|
}
|
|
396
406
|
/**
|
|
397
|
-
*
|
|
407
|
+
* Collects foreign keys from other tables that reference `table` (self-references are excluded — those travel with the rebuild).
|
|
408
|
+
* Foreign keys the referencing table drops or changes in the same diff are left to its own alter, and those of dropped tables are skipped.
|
|
398
409
|
*/
|
|
399
|
-
|
|
400
|
-
getInboundForeignKeys(fromSchema, table) {
|
|
410
|
+
getInboundForeignKeys(schemaDiff, table, dropsTables) {
|
|
401
411
|
const unqualified = (name) => name.split('.').pop();
|
|
402
412
|
const targetName = unqualified(table.name);
|
|
403
413
|
const ret = [];
|
|
404
|
-
for (const other of fromSchema.getTables()) {
|
|
405
|
-
if (other.name === table.name && other.schema === table.schema)
|
|
414
|
+
for (const other of schemaDiff.fromSchema.getTables()) {
|
|
415
|
+
if ((other.name === table.name && other.schema === table.schema) ||
|
|
416
|
+
(dropsTables && other.getShortestName() in schemaDiff.removedTables)) {
|
|
406
417
|
continue;
|
|
407
418
|
}
|
|
419
|
+
const otherDiff = schemaDiff.changedTables[other.getShortestName(false)];
|
|
408
420
|
for (const foreignKey of Object.values(other.getForeignKeys())) {
|
|
421
|
+
const name = foreignKey.constraintName;
|
|
422
|
+
if (otherDiff && (name in otherDiff.removedForeignKeys || name in otherDiff.changedForeignKeys)) {
|
|
423
|
+
continue;
|
|
424
|
+
}
|
|
409
425
|
if (unqualified(foreignKey.referencedTableName) === targetName) {
|
|
410
426
|
ret.push({ table: other, foreignKey });
|
|
411
427
|
}
|
|
@@ -413,6 +429,9 @@ export class SqlSchemaGenerator extends AbstractSchemaGenerator {
|
|
|
413
429
|
}
|
|
414
430
|
return ret;
|
|
415
431
|
}
|
|
432
|
+
/**
|
|
433
|
+
* We need to drop foreign keys first for all tables to allow dropping PK constraints.
|
|
434
|
+
*/
|
|
416
435
|
preAlterTable(diff, safe) {
|
|
417
436
|
const ret = [];
|
|
418
437
|
// removed/changed policies must be dropped before any column type alter, including the pre-alter
|
package/schema/partitioning.js
CHANGED
|
@@ -270,18 +270,18 @@ export const diffPartitioning = (from, to, defaultSchema) => {
|
|
|
270
270
|
if (!from) {
|
|
271
271
|
return true;
|
|
272
272
|
}
|
|
273
|
-
|
|
274
|
-
|
|
273
|
+
const toDefinition = normalizeQuotedIdentifiers(normalizePartitionDefinition(to.definition));
|
|
274
|
+
if (normalizeQuotedIdentifiers(normalizePartitionDefinition(from.definition)) !== toDefinition) {
|
|
275
275
|
return true;
|
|
276
276
|
}
|
|
277
|
-
|
|
277
|
+
// list/range partitions created outside of metadata (e.g. at runtime) are left unmanaged, hash partitions are not
|
|
278
|
+
if (toDefinition.startsWith('hash') && from.partitions.length !== to.partitions.length) {
|
|
278
279
|
return true;
|
|
279
280
|
}
|
|
280
281
|
const normalizeSchema = (schema) => (schema && schema !== defaultSchema ? schema : '');
|
|
281
282
|
const serializePartition = (partition) => `${normalizeSchema(partition.schema)}.${partition.name}:${normalizeQuotedIdentifiers(normalizePartitionBound(partition.bound))}`;
|
|
282
|
-
const fromPartitions = from.partitions.map(serializePartition)
|
|
283
|
-
|
|
284
|
-
return fromPartitions.some((partition, index) => partition !== toPartitions[index]);
|
|
283
|
+
const fromPartitions = new Set(from.partitions.map(serializePartition));
|
|
284
|
+
return to.partitions.some(partition => !fromPartitions.has(serializePartition(partition)));
|
|
285
285
|
};
|
|
286
286
|
const SUPPORTED_PARTITION_TYPES = ['hash', 'list', 'range'];
|
|
287
287
|
const isSupportedPartitionType = (value) => SUPPORTED_PARTITION_TYPES.includes(value);
|
package/typings.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Generated, Kysely } from 'kysely';
|
|
2
|
-
import type { CheckCallback, DeferMode, Dictionary, EntityName, EntityMetadata, EntityProperty, EntitySchemaWithMeta, ExtractDefineEntityProperties, FilterQuery, GroupOperator, IndexColumnOptions, InferEntityName, Opt, Primary, PrimaryProperty, QueryFlag, QueryOrderMap, RawQueryFragment, RoutineIgnoreField, Scalar, Type } from '@mikro-orm/core';
|
|
2
|
+
import type { CheckCallback, DeferMode, Dictionary, EntityName, EntityMetadata, EntityProperty, EntitySchemaWithMeta, ExtractDefineEntityProperties, FilterQuery, GroupOperator, IndexColumnOptions, InferEntityName, Opt, Primary, PrimaryProperty, QueryFlag, QueryOrderMap, RawQueryFragment, RoutineIgnoreField, Scalar, SequenceOptions, Type } from '@mikro-orm/core';
|
|
3
3
|
import type { JoinType, QueryType } from './query/enums.js';
|
|
4
4
|
import type { DatabaseSchema } from './schema/DatabaseSchema.js';
|
|
5
5
|
import type { DatabaseTable } from './schema/DatabaseTable.js';
|
|
@@ -37,6 +37,8 @@ export interface Column {
|
|
|
37
37
|
mappedType: Type<unknown>;
|
|
38
38
|
unsigned?: boolean;
|
|
39
39
|
autoincrement?: boolean;
|
|
40
|
+
/** applied only when the column is created, never diffed */
|
|
41
|
+
sequence?: SequenceOptions;
|
|
40
42
|
nullable?: boolean;
|
|
41
43
|
length?: number;
|
|
42
44
|
precision?: number;
|