@mikro-orm/sql 7.1.3-dev.4 → 7.1.3-dev.6
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/postgresql/PostgreSqlSchemaHelper.d.ts +14 -0
- package/dialects/postgresql/PostgreSqlSchemaHelper.js +64 -6
- package/package.json +2 -2
- package/schema/SchemaHelper.d.ts +11 -0
- package/schema/SchemaHelper.js +7 -0
- package/schema/SqlSchemaGenerator.d.ts +2 -0
- package/schema/SqlSchemaGenerator.js +28 -3
- package/schema/partitioning.js +5 -2
|
@@ -97,6 +97,20 @@ export declare class PostgreSqlSchemaHelper extends SchemaHelper {
|
|
|
97
97
|
private getEnumDefinitions;
|
|
98
98
|
protected getCollateSQL(collation: string): string;
|
|
99
99
|
createTableColumn(column: Column, table: DatabaseTable): string | undefined;
|
|
100
|
+
/**
|
|
101
|
+
* Adding partitioning to an existing table (or changing an existing definition) can't be done in place in
|
|
102
|
+
* PostgreSQL, so we rebuild it: park the original table in a temp schema (its indexes/constraints/sequences
|
|
103
|
+
* move with it, freeing the names), create the new partitioned table, copy the data across, and restore the
|
|
104
|
+
* foreign keys that pointed at it. In safe mode the original table is kept in the temp schema for manual
|
|
105
|
+
* verification; otherwise the temp schema is dropped at the end.
|
|
106
|
+
*/
|
|
107
|
+
getPartitioningRebuildSQL(rebuilds: {
|
|
108
|
+
diff: TableDifference;
|
|
109
|
+
inboundForeignKeys: {
|
|
110
|
+
table: DatabaseTable;
|
|
111
|
+
foreignKey: ForeignKey;
|
|
112
|
+
}[];
|
|
113
|
+
}[], safe: boolean): string[];
|
|
100
114
|
getPreAlterTable(tableDiff: TableDifference, safe: boolean): string[];
|
|
101
115
|
castColumn(name: string, type: string): string;
|
|
102
116
|
dropForeignKey(tableName: string, constraintName: string): string;
|
|
@@ -723,6 +723,7 @@ export class PostgreSqlSchemaHelper extends SchemaHelper {
|
|
|
723
723
|
join pg_namespace nsp2 on nsp2.oid = cls2.relnamespace
|
|
724
724
|
where (${[...tablesBySchemas.entries()].map(([schema, tables]) => `(cls1.relname in (${tables.map(t => this.platform.quoteValue(t.table_name)).join(',')}) and nsp1.nspname = ${this.platform.quoteValue(schema)})`).join(' or ')})
|
|
725
725
|
and confrelid > 0
|
|
726
|
+
and con.conparentid = 0
|
|
726
727
|
order by nsp1.nspname, cls1.relname, constraint_name, ord`;
|
|
727
728
|
const allFks = await connection.execute(sql, [], 'all', ctx);
|
|
728
729
|
const ret = {};
|
|
@@ -885,13 +886,70 @@ export class PostgreSqlSchemaHelper extends SchemaHelper {
|
|
|
885
886
|
Utils.runIfNotEmpty(() => col.push(`default ${column.default}`), useDefault);
|
|
886
887
|
return col.join(' ');
|
|
887
888
|
}
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
889
|
+
/**
|
|
890
|
+
* Adding partitioning to an existing table (or changing an existing definition) can't be done in place in
|
|
891
|
+
* PostgreSQL, so we rebuild it: park the original table in a temp schema (its indexes/constraints/sequences
|
|
892
|
+
* move with it, freeing the names), create the new partitioned table, copy the data across, and restore the
|
|
893
|
+
* foreign keys that pointed at it. In safe mode the original table is kept in the temp schema for manual
|
|
894
|
+
* verification; otherwise the temp schema is dropped at the end.
|
|
895
|
+
*/
|
|
896
|
+
getPartitioningRebuildSQL(rebuilds, safe) {
|
|
897
|
+
if (rebuilds.length === 0) {
|
|
898
|
+
return [];
|
|
899
|
+
}
|
|
900
|
+
const tmpSchema = 'mikro_orm_partition_swap';
|
|
901
|
+
const ret = [];
|
|
902
|
+
ret.push(`-- WARNING: changing partitioning rebuilds the table by copying all rows into a new partitioned table under an exclusive lock.`);
|
|
903
|
+
ret.push(`-- Review for data volume, locking and downtime before running.`);
|
|
904
|
+
ret.push(`create schema if not exists ${this.quote(tmpSchema)}`);
|
|
905
|
+
for (const { diff, inboundForeignKeys } of rebuilds) {
|
|
906
|
+
const table = diff.toTable;
|
|
907
|
+
const parked = `${this.quote(tmpSchema)}.${this.quote(table.name)}`;
|
|
908
|
+
// drop foreign keys that reference this table so it can be parked and replaced
|
|
909
|
+
for (const { table: localTable, foreignKey } of inboundForeignKeys) {
|
|
910
|
+
ret.push(`alter table ${localTable.getQuotedName()} drop constraint ${this.quote(foreignKey.constraintName)}`);
|
|
911
|
+
}
|
|
912
|
+
// move the existing table out of the way; its indexes/constraints/sequences travel with it
|
|
913
|
+
ret.push(`alter table ${table.getQuotedName()} set schema ${this.quote(tmpSchema)}`);
|
|
914
|
+
// child partitions are separate tables that don't move with the parent — park them too so their
|
|
915
|
+
// names are free for the new partitions
|
|
916
|
+
for (const partition of diff.fromTable.getPartitioning()?.partitions ?? []) {
|
|
917
|
+
const partitionName = this.quote(this.getTableName(partition.name, partition.schema ?? table.schema));
|
|
918
|
+
ret.push(`alter table ${partitionName} set schema ${this.quote(tmpSchema)}`);
|
|
919
|
+
}
|
|
920
|
+
// create the new partitioned table (+ partitions, indexes, checks)
|
|
921
|
+
this.append(ret, this.createTable(table, true));
|
|
922
|
+
// recreate the table's own foreign keys and triggers (createTable emits them separately)
|
|
923
|
+
if (this.options.createForeignKeyConstraints) {
|
|
924
|
+
for (const foreignKey of Object.values(table.getForeignKeys())) {
|
|
925
|
+
this.append(ret, this.createForeignKey(table, foreignKey));
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
for (const trigger of table.getTriggers()) {
|
|
929
|
+
this.append(ret, this.createTrigger(table, trigger));
|
|
930
|
+
}
|
|
931
|
+
// copy data for the columns that exist on both sides
|
|
932
|
+
const fromColumns = new Set(diff.fromTable.getColumns().map(col => col.name));
|
|
933
|
+
const columns = table
|
|
934
|
+
.getColumns()
|
|
935
|
+
.filter(col => fromColumns.has(col.name))
|
|
936
|
+
.map(col => this.quote(col.name))
|
|
937
|
+
.join(', ');
|
|
938
|
+
ret.push(`insert into ${table.getQuotedName()} (${columns}) select ${columns} from ${parked}`);
|
|
939
|
+
// restore the inbound foreign keys against the new table
|
|
940
|
+
for (const { table: localTable, foreignKey } of inboundForeignKeys) {
|
|
941
|
+
this.append(ret, this.createForeignKey(localTable, foreignKey));
|
|
942
|
+
}
|
|
894
943
|
}
|
|
944
|
+
if (safe) {
|
|
945
|
+
ret.push(`-- safe mode: original tables kept in schema "${tmpSchema}"; drop that schema manually once the data is verified`);
|
|
946
|
+
}
|
|
947
|
+
else {
|
|
948
|
+
ret.push(`drop schema if exists ${this.quote(tmpSchema)} cascade`);
|
|
949
|
+
}
|
|
950
|
+
return ret;
|
|
951
|
+
}
|
|
952
|
+
getPreAlterTable(tableDiff, safe) {
|
|
895
953
|
const ret = [];
|
|
896
954
|
const parts = tableDiff.name.split('.');
|
|
897
955
|
const tableName = parts.pop();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mikro-orm/sql",
|
|
3
|
-
"version": "7.1.3-dev.
|
|
3
|
+
"version": "7.1.3-dev.6",
|
|
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",
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
"@mikro-orm/core": "^7.1.2"
|
|
54
54
|
},
|
|
55
55
|
"peerDependencies": {
|
|
56
|
-
"@mikro-orm/core": "7.1.3-dev.
|
|
56
|
+
"@mikro-orm/core": "7.1.3-dev.6"
|
|
57
57
|
},
|
|
58
58
|
"engines": {
|
|
59
59
|
"node": ">= 22.17.0"
|
package/schema/SchemaHelper.d.ts
CHANGED
|
@@ -114,6 +114,17 @@ export declare abstract class SchemaHelper {
|
|
|
114
114
|
createTableColumn(column: Column, table: DatabaseTable, changedProperties?: Set<string>): string | undefined;
|
|
115
115
|
getPreAlterTable(tableDiff: TableDifference, safe: boolean): string[];
|
|
116
116
|
getPostAlterTable(tableDiff: TableDifference, safe: boolean): string[];
|
|
117
|
+
/**
|
|
118
|
+
* Generates a data-preserving rebuild for tables whose partitioning was added or changed. Empty by default;
|
|
119
|
+
* only PostgreSQL introspects partitioning, so this is never invoked for other platforms.
|
|
120
|
+
*/
|
|
121
|
+
getPartitioningRebuildSQL(rebuilds: {
|
|
122
|
+
diff: TableDifference;
|
|
123
|
+
inboundForeignKeys: {
|
|
124
|
+
table: DatabaseTable;
|
|
125
|
+
foreignKey: ForeignKey;
|
|
126
|
+
}[];
|
|
127
|
+
}[], safe: boolean): string[];
|
|
117
128
|
getChangeColumnCommentSQL(tableName: string, to: Column, schemaName?: string): string;
|
|
118
129
|
getNamespaces(connection: AbstractSqlConnection, ctx?: Transaction): Promise<string[]>;
|
|
119
130
|
protected mapIndexes(indexes: IndexDef[]): Promise<IndexDef[]>;
|
package/schema/SchemaHelper.js
CHANGED
|
@@ -580,6 +580,13 @@ export class SchemaHelper {
|
|
|
580
580
|
getPostAlterTable(tableDiff, safe) {
|
|
581
581
|
return [];
|
|
582
582
|
}
|
|
583
|
+
/**
|
|
584
|
+
* Generates a data-preserving rebuild for tables whose partitioning was added or changed. Empty by default;
|
|
585
|
+
* only PostgreSQL introspects partitioning, so this is never invoked for other platforms.
|
|
586
|
+
*/
|
|
587
|
+
getPartitioningRebuildSQL(rebuilds, safe) {
|
|
588
|
+
return [];
|
|
589
|
+
}
|
|
583
590
|
getChangeColumnCommentSQL(tableName, to, schemaName) {
|
|
584
591
|
return '';
|
|
585
592
|
}
|
|
@@ -40,6 +40,8 @@ export declare class SqlSchemaGenerator extends AbstractSchemaGenerator<Abstract
|
|
|
40
40
|
/**
|
|
41
41
|
* We need to drop foreign keys first for all tables to allow dropping PK constraints.
|
|
42
42
|
*/
|
|
43
|
+
/** Collects foreign keys from other tables that reference `table` (self-references are excluded — those travel with the rebuild). */
|
|
44
|
+
private getInboundForeignKeys;
|
|
43
45
|
private preAlterTable;
|
|
44
46
|
/**
|
|
45
47
|
* creates new database and connects to it
|
|
@@ -336,13 +336,21 @@ export class SqlSchemaGenerator extends AbstractSchemaGenerator {
|
|
|
336
336
|
ret.push('');
|
|
337
337
|
}
|
|
338
338
|
}
|
|
339
|
-
|
|
339
|
+
// Tables whose partitioning was added/changed can't be altered in place — they are rebuilt as a unit
|
|
340
|
+
// (the helper handles the data-preserving swap), so keep them out of the regular alter passes.
|
|
341
|
+
const changedTables = Object.values(schemaDiff.changedTables);
|
|
342
|
+
const partitioningRebuilds = changedTables
|
|
343
|
+
.filter(changedTable => changedTable.changedPartitioning)
|
|
344
|
+
.map(diff => ({ diff, inboundForeignKeys: this.getInboundForeignKeys(schemaDiff.fromSchema, diff.toTable) }));
|
|
345
|
+
const alteredTables = changedTables.filter(changedTable => !changedTable.changedPartitioning);
|
|
346
|
+
this.append(ret, this.helper.getPartitioningRebuildSQL(partitioningRebuilds, options.safe), true);
|
|
347
|
+
for (const changedTable of alteredTables) {
|
|
340
348
|
this.append(ret, this.preAlterTable(changedTable, options.safe), true);
|
|
341
349
|
}
|
|
342
|
-
for (const changedTable of
|
|
350
|
+
for (const changedTable of alteredTables) {
|
|
343
351
|
this.append(ret, this.helper.alterTable(changedTable, options.safe), true);
|
|
344
352
|
}
|
|
345
|
-
for (const changedTable of
|
|
353
|
+
for (const changedTable of alteredTables) {
|
|
346
354
|
this.append(ret, this.helper.getPostAlterTable(changedTable, options.safe), true);
|
|
347
355
|
}
|
|
348
356
|
if (!options.safe && this.platform.supportsNativeEnums()) {
|
|
@@ -382,6 +390,23 @@ export class SqlSchemaGenerator extends AbstractSchemaGenerator {
|
|
|
382
390
|
/**
|
|
383
391
|
* We need to drop foreign keys first for all tables to allow dropping PK constraints.
|
|
384
392
|
*/
|
|
393
|
+
/** Collects foreign keys from other tables that reference `table` (self-references are excluded — those travel with the rebuild). */
|
|
394
|
+
getInboundForeignKeys(fromSchema, table) {
|
|
395
|
+
const unqualified = (name) => name.split('.').pop();
|
|
396
|
+
const targetName = unqualified(table.name);
|
|
397
|
+
const ret = [];
|
|
398
|
+
for (const other of fromSchema.getTables()) {
|
|
399
|
+
if (other.name === table.name && other.schema === table.schema) {
|
|
400
|
+
continue;
|
|
401
|
+
}
|
|
402
|
+
for (const foreignKey of Object.values(other.getForeignKeys())) {
|
|
403
|
+
if (unqualified(foreignKey.referencedTableName) === targetName) {
|
|
404
|
+
ret.push({ table: other, foreignKey });
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
return ret;
|
|
409
|
+
}
|
|
385
410
|
preAlterTable(diff, safe) {
|
|
386
411
|
const ret = [];
|
|
387
412
|
this.append(ret, this.helper.getPreAlterTable(diff, safe));
|
package/schema/partitioning.js
CHANGED
|
@@ -261,10 +261,13 @@ export const getTablePartitioning = (meta, tableSchema, quoteIdentifier = id =>
|
|
|
261
261
|
};
|
|
262
262
|
/** @internal */
|
|
263
263
|
export const diffPartitioning = (from, to, defaultSchema) => {
|
|
264
|
-
|
|
264
|
+
// Metadata does not declare `partitionBy`, so partitioning is left unmanaged — never drop or alter an
|
|
265
|
+
// existing table's partitioning just because the entity omits it (in-place removal isn't supported anyway).
|
|
266
|
+
if (!to) {
|
|
265
267
|
return false;
|
|
266
268
|
}
|
|
267
|
-
|
|
269
|
+
// Metadata declares partitioning the table does not have yet (adding it in place isn't supported).
|
|
270
|
+
if (!from) {
|
|
268
271
|
return true;
|
|
269
272
|
}
|
|
270
273
|
if (normalizeQuotedIdentifiers(normalizePartitionDefinition(from.definition)) !==
|