From c0287e49d8a81a93ef1fdff9fadf7ca10ee8eb4c Mon Sep 17 00:00:00 2001 From: Stela Augustinova Date: Wed, 3 Dec 2025 14:28:58 +0100 Subject: [PATCH 01/21] FK test --- integration-tests/__tests__/alter-table.spec.js | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/integration-tests/__tests__/alter-table.spec.js b/integration-tests/__tests__/alter-table.spec.js index 62e3df85f..2d092b7d8 100644 --- a/integration-tests/__tests__/alter-table.spec.js +++ b/integration-tests/__tests__/alter-table.spec.js @@ -26,13 +26,15 @@ function pickImportantTableInfo(engine, table) { .map(props => _.omitBy(props, (v, k) => k == 'defaultValue' && v == 'NULL' && engine.setNullDefaultInsteadOfDrop) ), - // foreignKeys: table.foreignKeys - // .sort((a, b) => a.refTableName.localeCompare(b.refTableName)) - // .map(fk => ({ - // constraintType: fk.constraintType, - // refTableName: fk.refTableName, - // columns: fk.columns.map(col => ({ columnName: col.columnName, refColumnName: col.refColumnName })), - // })), + + // TODO: + foreignKeys: table.foreignKeys + .sort((a, b) => a.refTableName.localeCompare(b.refTableName)) + .map(fk => ({ + constraintType: fk.constraintType, + refTableName: fk.refTableName, + columns: fk.columns.map(col => ({ columnName: col.columnName, refColumnName: col.refColumnName })), + })), }; } @@ -103,6 +105,7 @@ async function testTableDiff(engine, conn, driver, mangle, changedTable = 't1') await driver.script(conn, sql); + // TODO: // if (!engine.skipIncrementalAnalysis) { // const structure2RealIncremental = await driver.analyseIncremental(conn, structure1Source); // checkTableStructure(engine, tget(structure2RealIncremental), tget(structure2)); From 251609e27431bf726699756cf26e4431a7fcd61c Mon Sep 17 00:00:00 2001 From: Stela Augustinova Date: Wed, 3 Dec 2025 15:21:12 +0100 Subject: [PATCH 02/21] Update foreign key references when dropping or renaming columns in alter table tests --- integration-tests/__tests__/alter-table.spec.js | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/integration-tests/__tests__/alter-table.spec.js b/integration-tests/__tests__/alter-table.spec.js index 2d092b7d8..9880f1022 100644 --- a/integration-tests/__tests__/alter-table.spec.js +++ b/integration-tests/__tests__/alter-table.spec.js @@ -182,7 +182,15 @@ describe('Alter table', () => { )( 'Drop column - %s - %s', testWrapper(async (conn, driver, column, engine) => { - await testTableDiff(engine, conn, driver, tbl => (tbl.columns = tbl.columns.filter(x => x.columnName != column))); + await testTableDiff(engine, conn, driver, + tbl => { + tbl.columns = tbl.columns.filter(x => x.columnName != column); + tbl.foreignKeys = tbl.foreignKeys.map(fk => ({ + ...fk, + columns: fk.columns.filter(col => col.columnName != column) + })).filter(fk => fk.columns.length > 0); + } + ); }) ); @@ -205,7 +213,11 @@ describe('Alter table', () => { engine, conn, driver, - tbl => (tbl.columns = tbl.columns.map(x => (x.columnName == column ? { ...x, columnName: 'col_renamed' } : x))) + tbl => { + tbl.columns = tbl.columns.map(x => (x.columnName == column ? { ...x, columnName: 'col_renamed' } : x)); + tbl.foreignKeys = tbl.foreignKeys.map(fk => ({...fk, columns: fk.columns.map(col => col.columnName == column ? { ...col, columnName: 'col_renamed' } : col) + })); + } ); }) ); From fa5fda0c3b1db695455e72f8b3e9e2f3fba06e8d Mon Sep 17 00:00:00 2001 From: Stela Augustinova Date: Wed, 3 Dec 2025 15:31:05 +0100 Subject: [PATCH 03/21] Incremental analysis --- integration-tests/__tests__/alter-table.spec.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/integration-tests/__tests__/alter-table.spec.js b/integration-tests/__tests__/alter-table.spec.js index 9880f1022..8f403245d 100644 --- a/integration-tests/__tests__/alter-table.spec.js +++ b/integration-tests/__tests__/alter-table.spec.js @@ -106,10 +106,10 @@ async function testTableDiff(engine, conn, driver, mangle, changedTable = 't1') await driver.script(conn, sql); // TODO: - // if (!engine.skipIncrementalAnalysis) { - // const structure2RealIncremental = await driver.analyseIncremental(conn, structure1Source); - // checkTableStructure(engine, tget(structure2RealIncremental), tget(structure2)); - // } + if (!engine.skipIncrementalAnalysis) { + const structure2RealIncremental = await driver.analyseIncremental(conn, structure1Source); + checkTableStructure(engine, tget(structure2RealIncremental), tget(structure2)); + } const structure2Real = extendDatabaseInfo(await driver.analyseFull(conn)); From ace1cec1f65b1170213d039b0b6c7e99a7d9b230 Mon Sep 17 00:00:00 2001 From: Stela Augustinova Date: Thu, 4 Dec 2025 10:00:48 +0100 Subject: [PATCH 04/21] Delete FK from drop column --- integration-tests/__tests__/alter-table.spec.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/integration-tests/__tests__/alter-table.spec.js b/integration-tests/__tests__/alter-table.spec.js index 8f403245d..5d57d4223 100644 --- a/integration-tests/__tests__/alter-table.spec.js +++ b/integration-tests/__tests__/alter-table.spec.js @@ -185,10 +185,12 @@ describe('Alter table', () => { await testTableDiff(engine, conn, driver, tbl => { tbl.columns = tbl.columns.filter(x => x.columnName != column); - tbl.foreignKeys = tbl.foreignKeys.map(fk => ({ - ...fk, - columns: fk.columns.filter(col => col.columnName != column) - })).filter(fk => fk.columns.length > 0); + tbl.foreignKeys = tbl.foreignKeys + .map(fk => ({ + ...fk, + columns: fk.columns.filter(col => col.columnName != column) + })) + .filter(fk => fk.columns.length > 0); } ); }) From fdbd08f511c61a739eeb77a91a12de1603a12e76 Mon Sep 17 00:00:00 2001 From: Stela Augustinova Date: Thu, 4 Dec 2025 13:00:06 +0100 Subject: [PATCH 05/21] Added FK constraint type for sqlite --- plugins/dbgate-plugin-sqlite/src/backend/Analyser.js | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/dbgate-plugin-sqlite/src/backend/Analyser.js b/plugins/dbgate-plugin-sqlite/src/backend/Analyser.js index 4dde1fb4a..fe9e951ff 100644 --- a/plugins/dbgate-plugin-sqlite/src/backend/Analyser.js +++ b/plugins/dbgate-plugin-sqlite/src/backend/Analyser.js @@ -129,6 +129,7 @@ class Analyser extends DatabaseAnalyser { updateAction: fkcol.on_update, deleteAction: fkcol.on_delete, constraintName: `FK_${tableName}_${fkcol.id}`, + constraintType: 'foreignKey', }; return fk; }); From 137eac7dbf0f47ea371402a4078db5a54f1955bd Mon Sep 17 00:00:00 2001 From: Stela Augustinova Date: Thu, 4 Dec 2025 14:07:55 +0100 Subject: [PATCH 06/21] Add dropColumnDependencies property to dialect configuration --- plugins/dbgate-plugin-mysql/src/frontend/drivers.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/dbgate-plugin-mysql/src/frontend/drivers.js b/plugins/dbgate-plugin-mysql/src/frontend/drivers.js index 9ae5c4946..0ed90f494 100644 --- a/plugins/dbgate-plugin-mysql/src/frontend/drivers.js +++ b/plugins/dbgate-plugin-mysql/src/frontend/drivers.js @@ -46,6 +46,8 @@ const dialect = { dropReferencesWhenDropTable: false, requireStandaloneSelectForScopeIdentity: true, + dropColumnDependencies: ['dependencies'], + columnProperties: { columnComment: true, isUnsigned: true, From aba660eddbf9af49aeef1ba3828def0dedcf0865 Mon Sep 17 00:00:00 2001 From: Stela Augustinova Date: Thu, 4 Dec 2025 14:08:22 +0100 Subject: [PATCH 07/21] Fix nullability filter in alter table tests --- integration-tests/__tests__/alter-table.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integration-tests/__tests__/alter-table.spec.js b/integration-tests/__tests__/alter-table.spec.js index 5d57d4223..1926475bb 100644 --- a/integration-tests/__tests__/alter-table.spec.js +++ b/integration-tests/__tests__/alter-table.spec.js @@ -196,7 +196,7 @@ describe('Alter table', () => { }) ); - test.each(createEnginesColumnsSource(engines.filter(x => !x.skipNullable && !x.skipChangeNullability)))( + test.each(createEnginesColumnsSource(engines.filter(x => !x.skipNullability && !x.skipChangeNullability)))( 'Change nullability - %s - %s', testWrapper(async (conn, driver, column, engine) => { await testTableDiff( From 3b1c8748f141e6e8301a9d0183a4c96d28965ea5 Mon Sep 17 00:00:00 2001 From: Stela Augustinova Date: Thu, 4 Dec 2025 14:34:26 +0100 Subject: [PATCH 08/21] Add createForeignKeyFore method to handle foreign key creation (Oracle) --- .../dbgate-plugin-oracle/src/frontend/Dumper.js | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/plugins/dbgate-plugin-oracle/src/frontend/Dumper.js b/plugins/dbgate-plugin-oracle/src/frontend/Dumper.js index 84d2ba84b..1c7aef034 100644 --- a/plugins/dbgate-plugin-oracle/src/frontend/Dumper.js +++ b/plugins/dbgate-plugin-oracle/src/frontend/Dumper.js @@ -64,6 +64,21 @@ class Dumper extends SqlDumper { this.putCmd('^alter ^table %f ^rename ^column %i ^to %i', column, column.columnName, newcol); } + createForeignKeyFore(fk) { + if (fk.constraintName != null && !this.dialect.anonymousForeignKey) { + this.put('^constraint %i ', fk.constraintName); + } + this.put( + '^foreign ^key (%,i) ^references %f (%,i)', + fk.columns.map(x => x.columnName), + { schemaName: fk.refSchemaName, pureName: fk.refTableName }, + fk.columns.map(x => x.refColumnName) + ); + if (fk.deleteAction && fk.deleteAction.toUpperCase() !== 'NO ACTION') { + this.put(' ^on ^delete %k', fk.deleteAction); + } + } + // dropTable(obj, options = {}) { // this.put('^drop ^table'); // if (options.testIfExists) this.put(' ^if ^exists'); From b3130225b561f26e38b9c75d43ef201e78be0264 Mon Sep 17 00:00:00 2001 From: Stela Augustinova Date: Thu, 4 Dec 2025 14:53:08 +0100 Subject: [PATCH 09/21] Filter out primary key columns in nullability change tests --- integration-tests/__tests__/alter-table.spec.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/integration-tests/__tests__/alter-table.spec.js b/integration-tests/__tests__/alter-table.spec.js index 1926475bb..2daf16730 100644 --- a/integration-tests/__tests__/alter-table.spec.js +++ b/integration-tests/__tests__/alter-table.spec.js @@ -196,7 +196,11 @@ describe('Alter table', () => { }) ); - test.each(createEnginesColumnsSource(engines.filter(x => !x.skipNullability && !x.skipChangeNullability)))( + test.each( + createEnginesColumnsSource(engines.filter(x => !x.skipNullability && !x.skipChangeNullability)).filter( + ([_label, col]) => !col.endsWith('_pk') + ) + )( 'Change nullability - %s - %s', testWrapper(async (conn, driver, column, engine) => { await testTableDiff( From 851d2e915165f81af5829a99f7f6829cefaacbf7 Mon Sep 17 00:00:00 2001 From: Jan Prochazka Date: Mon, 8 Dec 2025 13:05:38 +0100 Subject: [PATCH 10/21] fixed double drop constraint --- integration-tests/__tests__/alter-table.spec.js | 1 + packages/tools/src/alterPlan.ts | 8 +++++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/integration-tests/__tests__/alter-table.spec.js b/integration-tests/__tests__/alter-table.spec.js index 2daf16730..cb8c198f4 100644 --- a/integration-tests/__tests__/alter-table.spec.js +++ b/integration-tests/__tests__/alter-table.spec.js @@ -119,6 +119,7 @@ async function testTableDiff(engine, conn, driver, mangle, changedTable = 't1') const TESTED_COLUMNS = ['col_pk', 'col_std', 'col_def', 'col_fk', 'col_ref', 'col_idx', 'col_uq']; // const TESTED_COLUMNS = ['col_pk']; +// const TESTED_COLUMNS = ['col_fk']; // const TESTED_COLUMNS = ['col_idx']; // const TESTED_COLUMNS = ['col_def']; // const TESTED_COLUMNS = ['col_std']; diff --git a/packages/tools/src/alterPlan.ts b/packages/tools/src/alterPlan.ts index 5462efab8..1143ff165 100644 --- a/packages/tools/src/alterPlan.ts +++ b/packages/tools/src/alterPlan.ts @@ -337,7 +337,13 @@ export class AlterPlan { return opRes; }), op, - ]; + ].filter(op => { + // filter duplicated drops + const existingDrop = this.operations.find( + o => o.operationType == 'dropConstraint' && o.oldObject === op['oldObject'] + ); + return existingDrop == null; + }); return res; } From 85b7e3ebe30810fe1c8e3be3892e965de5c49665 Mon Sep 17 00:00:00 2001 From: Jan Prochazka Date: Mon, 8 Dec 2025 14:18:28 +0100 Subject: [PATCH 11/21] fixed sqlite test folder --- integration-tests/tools.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integration-tests/tools.js b/integration-tests/tools.js index a1a14dfd9..de24d8448 100644 --- a/integration-tests/tools.js +++ b/integration-tests/tools.js @@ -22,7 +22,7 @@ async function connect(engine, database) { if (engine.generateDbFile) { const conn = await driver.connect({ ...connection, - databaseFile: (engine.databaseFileLocationOnServer ?? 'dbtemp/') + database, + databaseFile: (engine.databaseFileLocationOnServer ?? 'integration-tests/dbtemp/') + database, }); return conn; } else { From 4d61c74a8b9b21c6f1a98b500b04439a35a71641 Mon Sep 17 00:00:00 2001 From: Jan Prochazka Date: Mon, 8 Dec 2025 14:34:45 +0100 Subject: [PATCH 12/21] fixed tes on CI --- integration-tests/tools.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/integration-tests/tools.js b/integration-tests/tools.js index de24d8448..55b316e8a 100644 --- a/integration-tests/tools.js +++ b/integration-tests/tools.js @@ -22,7 +22,9 @@ async function connect(engine, database) { if (engine.generateDbFile) { const conn = await driver.connect({ ...connection, - databaseFile: (engine.databaseFileLocationOnServer ?? 'integration-tests/dbtemp/') + database, + databaseFile: + (engine.databaseFileLocationOnServer ?? (process.env.CITEST ? 'dbtemp/' : 'integration-tests/dbtemp/')) + + database, }); return conn; } else { From 7579f6e42abd76492d6059709833c6488730b8e3 Mon Sep 17 00:00:00 2001 From: Stela Augustinova Date: Mon, 8 Dec 2025 15:19:27 +0100 Subject: [PATCH 13/21] fix: comment out incremental analysis in testTableDiff and correct clickhouse image reference --- integration-tests/__tests__/alter-table.spec.js | 8 ++++---- integration-tests/docker-compose.yaml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/integration-tests/__tests__/alter-table.spec.js b/integration-tests/__tests__/alter-table.spec.js index cb8c198f4..1aec9af5b 100644 --- a/integration-tests/__tests__/alter-table.spec.js +++ b/integration-tests/__tests__/alter-table.spec.js @@ -106,10 +106,10 @@ async function testTableDiff(engine, conn, driver, mangle, changedTable = 't1') await driver.script(conn, sql); // TODO: - if (!engine.skipIncrementalAnalysis) { - const structure2RealIncremental = await driver.analyseIncremental(conn, structure1Source); - checkTableStructure(engine, tget(structure2RealIncremental), tget(structure2)); - } + // if (!engine.skipIncrementalAnalysis) { + // const structure2RealIncremental = await driver.analyseIncremental(conn, structure1Source); + // checkTableStructure(engine, tget(structure2RealIncremental), tget(structure2)); + // } const structure2Real = extendDatabaseInfo(await driver.analyseFull(conn)); diff --git a/integration-tests/docker-compose.yaml b/integration-tests/docker-compose.yaml index c64836a4d..e48b5ddb1 100644 --- a/integration-tests/docker-compose.yaml +++ b/integration-tests/docker-compose.yaml @@ -44,7 +44,7 @@ services: # - 15942:9042 # # clickhouse: - # image: bitnami/clickhouse:24.8.4 + # image: bitnamilegacy/clickhouse:24.8.4 # restart: always # ports: # - 15005:8123 From 12e6afbaadfac0af65d53ec4f599c26f7e029fc4 Mon Sep 17 00:00:00 2001 From: Stela Augustinova Date: Mon, 8 Dec 2025 15:27:45 +0100 Subject: [PATCH 14/21] fix: correct reference from wholeNewDb to wholeOldDb in AlterPlan class --- packages/tools/src/alterPlan.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/tools/src/alterPlan.ts b/packages/tools/src/alterPlan.ts index 1143ff165..e8fd88ae3 100644 --- a/packages/tools/src/alterPlan.ts +++ b/packages/tools/src/alterPlan.ts @@ -504,7 +504,7 @@ export class AlterPlan { return []; } - const table = this.wholeNewDb.tables.find( + const table = this.wholeOldDb.tables.find( x => x.pureName == op[objectField].pureName && x.schemaName == op[objectField].schemaName ); this.recreates.tables += 1; From 1b5646f526e54a921188160b09b21cf8e2cfd7ad Mon Sep 17 00:00:00 2001 From: Jan Prochazka Date: Mon, 8 Dec 2025 16:05:50 +0100 Subject: [PATCH 15/21] Revert "fix: correct reference from wholeNewDb to wholeOldDb in AlterPlan class" This reverts commit 12e6afbaadfac0af65d53ec4f599c26f7e029fc4. --- packages/tools/src/alterPlan.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/tools/src/alterPlan.ts b/packages/tools/src/alterPlan.ts index e8fd88ae3..1143ff165 100644 --- a/packages/tools/src/alterPlan.ts +++ b/packages/tools/src/alterPlan.ts @@ -504,7 +504,7 @@ export class AlterPlan { return []; } - const table = this.wholeOldDb.tables.find( + const table = this.wholeNewDb.tables.find( x => x.pureName == op[objectField].pureName && x.schemaName == op[objectField].schemaName ); this.recreates.tables += 1; From 52dce7dfd356f17361964a67cdcec26dfb7391ff Mon Sep 17 00:00:00 2001 From: Jan Prochazka Date: Mon, 8 Dec 2025 16:41:55 +0100 Subject: [PATCH 16/21] disabled grouping recreate table OPs --- packages/tools/src/alterPlan.ts | 101 +++++++++++++++++--------------- 1 file changed, 54 insertions(+), 47 deletions(-) diff --git a/packages/tools/src/alterPlan.ts b/packages/tools/src/alterPlan.ts index 1143ff165..a609042fa 100644 --- a/packages/tools/src/alterPlan.ts +++ b/packages/tools/src/alterPlan.ts @@ -91,8 +91,8 @@ interface AlterOperation_RenameConstraint { } interface AlterOperation_RecreateTable { operationType: 'recreateTable'; - table: TableInfo; - operations: AlterOperation[]; + oldTable: TableInfo; + newTable: TableInfo; } interface AlterOperation_FillPreloadedRows { operationType: 'fillPreloadedRows'; @@ -249,11 +249,11 @@ export class AlterPlan { }); } - recreateTable(table: TableInfo, operations: AlterOperation[]) { + recreateTable(oldTable: TableInfo, newTable: TableInfo) { this.operations.push({ operationType: 'recreateTable', - table, - operations, + oldTable, + newTable, }); this.recreates.tables += 1; } @@ -504,15 +504,19 @@ export class AlterPlan { return []; } - const table = this.wholeNewDb.tables.find( + const oldTable = this.wholeOldDb.tables.find( + x => x.pureName == op[objectField].pureName && x.schemaName == op[objectField].schemaName + ); + const newTable = this.wholeNewDb.tables.find( x => x.pureName == op[objectField].pureName && x.schemaName == op[objectField].schemaName ); this.recreates.tables += 1; return [ { operationType: 'recreateTable', - table, - operations: [op], + oldTable, + newTable, + // operations: [op], }, ]; } @@ -520,35 +524,38 @@ export class AlterPlan { } _groupTableRecreations(): AlterOperation[] { - const res = []; - const recreates = {}; - for (const op of this.operations) { - if (op.operationType == 'recreateTable' && op.table) { - const existingRecreate = recreates[`${op.table.schemaName}||${op.table.pureName}`]; - if (existingRecreate) { - existingRecreate.operations.push(...op.operations); - } else { - const recreate = { - ...op, - operations: [...op.operations], - }; - res.push(recreate); - recreates[`${op.table.schemaName}||${op.table.pureName}`] = recreate; - } - } else { - // @ts-ignore - const oldObject: TableInfo = op.oldObject || op.object; - if (oldObject) { - const recreated = recreates[`${oldObject.schemaName}||${oldObject.pureName}`]; - if (recreated) { - recreated.operations.push(op); - continue; - } - } - res.push(op); - } - } - return res; + return this.operations; + + // this is not implemented now + // const res = []; + // const recreates = {}; + // for (const op of this.operations) { + // if (op.operationType == 'recreateTable' && op.table) { + // const existingRecreate = recreates[`${op.table.schemaName}||${op.table.pureName}`]; + // if (existingRecreate) { + // existingRecreate.operations.push(...op.operations); + // } else { + // const recreate = { + // ...op, + // operations: [...op.operations], + // }; + // res.push(recreate); + // recreates[`${op.table.schemaName}||${op.table.pureName}`] = recreate; + // } + // } else { + // // @ts-ignore + // const oldObject: TableInfo = op.oldObject || op.object; + // if (oldObject) { + // const recreated = recreates[`${oldObject.schemaName}||${oldObject.pureName}`]; + // if (recreated) { + // recreated.operations.push(op); + // continue; + // } + // } + // res.push(op); + // } + // } + // return res; } _moveForeignKeysToLast(): AlterOperation[] { @@ -679,16 +686,16 @@ export function runAlterOperation(op: AlterOperation, processor: AlterProcessor) break; case 'recreateTable': { - const oldTable = generateTablePairingId(op.table); - const newTable = _.cloneDeep(oldTable); - const newDb = DatabaseAnalyser.createEmptyStructure(); - newDb.tables.push(newTable); - // console.log('////////////////////////////newTable1', newTable); - op.operations.forEach(child => runAlterOperation(child, new DatabaseInfoAlterProcessor(newDb))); - // console.log('////////////////////////////op.operations', op.operations); - // console.log('////////////////////////////op.table', op.table); - // console.log('////////////////////////////newTable2', newTable); - processor.recreateTable(oldTable, newTable); + // const oldTable = generateTablePairingId(op.table); + // const newTable = _.cloneDeep(oldTable); + // const newDb = DatabaseAnalyser.createEmptyStructure(); + // newDb.tables.push(newTable); + // // console.log('////////////////////////////newTable1', newTable); + // op.operations.forEach(child => runAlterOperation(child, new DatabaseInfoAlterProcessor(newDb))); + // // console.log('////////////////////////////op.operations', op.operations); + // // console.log('////////////////////////////op.table', op.table); + // // console.log('////////////////////////////newTable2', newTable); + processor.recreateTable(op.oldTable, op.newTable); } break; } From 8d4178b9841139e4978efe677ac788c6ce395107 Mon Sep 17 00:00:00 2001 From: Jan Prochazka Date: Mon, 8 Dec 2025 16:57:09 +0100 Subject: [PATCH 17/21] grouped table recreates --- packages/tools/src/alterPlan.ts | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/packages/tools/src/alterPlan.ts b/packages/tools/src/alterPlan.ts index a609042fa..8002739b5 100644 --- a/packages/tools/src/alterPlan.ts +++ b/packages/tools/src/alterPlan.ts @@ -524,9 +524,22 @@ export class AlterPlan { } _groupTableRecreations(): AlterOperation[] { - return this.operations; + const res = []; + const recreates = new Set(); + for (const op of this.operations) { + if (op.operationType == 'recreateTable' && op.oldTable && op.newTable) { + const key = `${op.oldTable.schemaName}||${op.oldTable.pureName}`; + if (recreates.has(key)) { + // prevent duplicate recreates + continue; + } + recreates.add(key); + } + + res.push(op); + } + return res; - // this is not implemented now // const res = []; // const recreates = {}; // for (const op of this.operations) { From b3943f005dd3ce4494f18e293a0def9fdcca8fde Mon Sep 17 00:00:00 2001 From: Jan Prochazka Date: Mon, 8 Dec 2025 17:35:29 +0100 Subject: [PATCH 18/21] alter table fixed --- packages/tools/src/alterPlan.ts | 50 +++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/packages/tools/src/alterPlan.ts b/packages/tools/src/alterPlan.ts index 8002739b5..a1a8dfb78 100644 --- a/packages/tools/src/alterPlan.ts +++ b/packages/tools/src/alterPlan.ts @@ -523,6 +523,54 @@ export class AlterPlan { return null; } + _removeRecreatedTableAlters(): AlterOperation[] { + const res: AlterOperation[] = []; + const recreates = new Set(); + for (const op of this.operations) { + if (op.operationType == 'recreateTable' && op.oldTable && op.newTable) { + const key = `${op.oldTable.schemaName}||${op.oldTable.pureName}`; + recreates.add(key); + } + } + + for (const op of this.operations) { + switch (op.operationType) { + case 'createColumn': + case 'createConstraint': + { + const key = `${op.newObject.schemaName}||${op.newObject.pureName}`; + if (recreates.has(key)) { + // skip create inside recreated table + continue; + } + } + break; + case 'dropColumn': + case 'dropConstraint': + case 'changeColumn': + { + const key = `${op.oldObject.schemaName}||${op.oldObject.pureName}`; + if (recreates.has(key)) { + // skip drop/change inside recreated table + continue; + } + } + break; + case 'renameColumn': + { + const key = `${op.object.schemaName}||${op.object.pureName}`; + if (recreates.has(key)) { + // skip rename inside recreated table + continue; + } + } + break; + } + res.push(op); + } + return res; + } + _groupTableRecreations(): AlterOperation[] { const res = []; const recreates = new Set(); @@ -637,6 +685,8 @@ export class AlterPlan { // console.log('*****************OPERATIONS3', this.operations); + this.operations = this._removeRecreatedTableAlters(); + this.operations = this._moveForeignKeysToLast(); // console.log('*****************OPERATIONS4', this.operations); From 98f5bb4124aa73375efb657be1bb770a2033b543 Mon Sep 17 00:00:00 2001 From: Jan Prochazka Date: Tue, 9 Dec 2025 10:45:38 +0100 Subject: [PATCH 19/21] sanitize constraints --- packages/tools/src/SqlDumper.ts | 84 +++++++++++++++++++++++++++++---- 1 file changed, 75 insertions(+), 9 deletions(-) diff --git a/packages/tools/src/SqlDumper.ts b/packages/tools/src/SqlDumper.ts index 9302fa368..e2e1f876e 100644 --- a/packages/tools/src/SqlDumper.ts +++ b/packages/tools/src/SqlDumper.ts @@ -26,6 +26,7 @@ import _isDate from 'lodash/isDate'; import _isArray from 'lodash/isArray'; import _isPlainObject from 'lodash/isPlainObject'; import _keys from 'lodash/keys'; +import _cloneDeep from 'lodash/cloneDeep'; import uuidv1 from 'uuid/v1'; export class SqlDumper implements AlterProcessor { @@ -666,6 +667,68 @@ export class SqlDumper implements AlterProcessor { } } + sanitizeTableConstraints(table: TableInfo): TableInfo { + // Create a deep copy of the table + const sanitized = _cloneDeep(table); + + // Get the set of existing column names + const existingColumns = new Set(sanitized.columns.map(col => col.columnName)); + + // Filter primary key columns to only include existing columns + if (sanitized.primaryKey) { + const validPkColumns = sanitized.primaryKey.columns.filter(col => existingColumns.has(col.columnName)); + if (validPkColumns.length === 0) { + // If no valid columns remain, remove the primary key entirely + sanitized.primaryKey = null; + } else if (validPkColumns.length < sanitized.primaryKey.columns.length) { + // Update primary key with only valid columns + sanitized.primaryKey = { + ...sanitized.primaryKey, + columns: validPkColumns + }; + } + } + + // Filter sorting key columns to only include existing columns + if (sanitized.sortingKey) { + const validSkColumns = sanitized.sortingKey.columns.filter(col => existingColumns.has(col.columnName)); + if (validSkColumns.length === 0) { + sanitized.sortingKey = null; + } else if (validSkColumns.length < sanitized.sortingKey.columns.length) { + sanitized.sortingKey = { + ...sanitized.sortingKey, + columns: validSkColumns + }; + } + } + + // Filter foreign keys to only include those with all columns present + if (sanitized.foreignKeys) { + sanitized.foreignKeys = sanitized.foreignKeys.filter(fk => + fk.columns.every(col => existingColumns.has(col.columnName)) + ); + } + + // Filter indexes to only include those with all columns present + if (sanitized.indexes) { + sanitized.indexes = sanitized.indexes.filter(idx => + idx.columns.every(col => existingColumns.has(col.columnName)) + ); + } + + // Filter unique constraints to only include those with all columns present + if (sanitized.uniques) { + sanitized.uniques = sanitized.uniques.filter(uq => + uq.columns.every(col => existingColumns.has(col.columnName)) + ); + } + + // Filter dependencies (references from other tables) - these should remain as-is + // since they don't affect the CREATE TABLE statement for this table + + return sanitized; + } + recreateTable(oldTable: TableInfo, newTable: TableInfo) { if (!oldTable.pairingId || !newTable.pairingId || oldTable.pairingId != newTable.pairingId) { throw new Error('Recreate is not possible: oldTable.paringId != newTable.paringId'); @@ -680,48 +743,51 @@ export class SqlDumper implements AlterProcessor { })) .filter(x => x.newcol); + // Create a sanitized version of newTable with constraints that only reference existing columns + const sanitizedNewTable = this.sanitizeTableConstraints(newTable); + if (this.driver.supportsTransactions) { this.dropConstraints(oldTable, true); this.renameTable(oldTable, tmpTable); - this.createTable(newTable); + this.createTable(sanitizedNewTable); - const autoinc = newTable.columns.find(x => x.autoIncrement); + const autoinc = sanitizedNewTable.columns.find(x => x.autoIncrement); if (autoinc) { - this.allowIdentityInsert(newTable, true); + this.allowIdentityInsert(sanitizedNewTable, true); } this.putCmd( '^insert ^into %f (%,i) select %,i ^from %f', - newTable, + sanitizedNewTable, columnPairs.map(x => x.newcol.columnName), columnPairs.map(x => x.oldcol.columnName), { ...oldTable, pureName: tmpTable } ); if (autoinc) { - this.allowIdentityInsert(newTable, false); + this.allowIdentityInsert(sanitizedNewTable, false); } if (this.dialect.dropForeignKey) { - newTable.dependencies.forEach(cnt => this.createConstraint(cnt)); + sanitizedNewTable.dependencies.forEach(cnt => this.createConstraint(cnt)); } this.dropTable({ ...oldTable, pureName: tmpTable }); } else { // we have to preserve old table as long as possible - this.createTable({ ...newTable, pureName: tmpTable }); + this.createTable({ ...sanitizedNewTable, pureName: tmpTable }); this.putCmd( '^insert ^into %f (%,i) select %,s ^from %f', - { ...newTable, pureName: tmpTable }, + { ...sanitizedNewTable, pureName: tmpTable }, columnPairs.map(x => x.newcol.columnName), columnPairs.map(x => x.oldcol.columnName), oldTable ); this.dropTable(oldTable); - this.renameTable({ ...newTable, pureName: tmpTable }, newTable.pureName); + this.renameTable({ ...sanitizedNewTable, pureName: tmpTable }, newTable.pureName); } } From 955ca99cf3b1f7e44f43153db6b02269e37884ce Mon Sep 17 00:00:00 2001 From: Jan Prochazka Date: Tue, 9 Dec 2025 10:46:25 +0100 Subject: [PATCH 20/21] try to comment out earlier patch --- packages/tools/src/alterPlan.ts | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/packages/tools/src/alterPlan.ts b/packages/tools/src/alterPlan.ts index a1a8dfb78..f4d9615fc 100644 --- a/packages/tools/src/alterPlan.ts +++ b/packages/tools/src/alterPlan.ts @@ -337,13 +337,14 @@ export class AlterPlan { return opRes; }), op, - ].filter(op => { - // filter duplicated drops - const existingDrop = this.operations.find( - o => o.operationType == 'dropConstraint' && o.oldObject === op['oldObject'] - ); - return existingDrop == null; - }); + ]; + // .filter(op => { + // // filter duplicated drops + // const existingDrop = this.operations.find( + // o => o.operationType == 'dropConstraint' && o.oldObject === op['oldObject'] + // ); + // return existingDrop == null; + // }) return res; } From 377cd645569b31a4445a26a00c0f6bc696d0ee02 Mon Sep 17 00:00:00 2001 From: Jan Prochazka Date: Tue, 9 Dec 2025 12:56:47 +0100 Subject: [PATCH 21/21] Revert "try to comment out earlier patch" This reverts commit 955ca99cf3b1f7e44f43153db6b02269e37884ce. --- packages/tools/src/alterPlan.ts | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/packages/tools/src/alterPlan.ts b/packages/tools/src/alterPlan.ts index f4d9615fc..a1a8dfb78 100644 --- a/packages/tools/src/alterPlan.ts +++ b/packages/tools/src/alterPlan.ts @@ -337,14 +337,13 @@ export class AlterPlan { return opRes; }), op, - ]; - // .filter(op => { - // // filter duplicated drops - // const existingDrop = this.operations.find( - // o => o.operationType == 'dropConstraint' && o.oldObject === op['oldObject'] - // ); - // return existingDrop == null; - // }) + ].filter(op => { + // filter duplicated drops + const existingDrop = this.operations.find( + o => o.operationType == 'dropConstraint' && o.oldObject === op['oldObject'] + ); + return existingDrop == null; + }); return res; }