diff --git a/integration-tests/__tests__/alter-processor.spec.js b/integration-tests/__tests__/alter-processor.spec.js index 3315b2114..a586562d0 100644 --- a/integration-tests/__tests__/alter-processor.spec.js +++ b/integration-tests/__tests__/alter-processor.spec.js @@ -24,16 +24,20 @@ async function testTableDiff(conn, driver, mangle) { await driver.query( conn, `create table t1 ( - id int not null primary key, + col_pk int not null primary key, col_std int null, col_def int null default 12, col_fk int null references t0(id), - col_idx int null + col_idx int null, + col_uq int null unique, + col_ref int null unique )` ); await driver.query(conn, `create index idx1 on t1(col_idx)`); + await driver.query(conn, `create table t2 (id int not null primary key, fkval int null references t1(col_ref))`); + const tget = x => x.tables.find(y => y.pureName == 't1'); const structure1 = generateDbPairingId(extendDatabaseInfo(await driver.analyseFull(conn))); let structure2 = _.cloneDeep(structure1); @@ -51,8 +55,10 @@ async function testTableDiff(conn, driver, mangle) { // expect(stableStringify(structure2)).toEqual(stableStringify(structure2Real)); } -// const TESTED_COLUMNS = ['col_std', 'col_def', 'col_fk', 'col_idx']; -const TESTED_COLUMNS = ['col_std']; +const TESTED_COLUMNS = ['col_pk', 'col_std', 'col_def', 'col_fk', 'col_ref', 'col_idx', 'col_uq']; +// const TESTED_COLUMNS = ['col_idx']; +// const TESTED_COLUMNS = ['col_fk']; +// const TESTED_COLUMNS = ['col_std']; function engines_columns_source() { return _.flatten(engines.map(engine => TESTED_COLUMNS.map(column => [engine.label, column, engine]))); diff --git a/packages/tools/src/SqlDumper.ts b/packages/tools/src/SqlDumper.ts index 7daa6dd32..d0e1ff7e3 100644 --- a/packages/tools/src/SqlDumper.ts +++ b/packages/tools/src/SqlDumper.ts @@ -350,14 +350,33 @@ export class SqlDumper implements AlterProcessor { changeTriggerSchema(obj: TriggerInfo, newSchema: string) {} renameTrigger(obj: TriggerInfo, newSchema: string) {} - dropConstraint(cnt: ConstraintInfo) { + dropConstraintCore(cnt: ConstraintInfo) { this.putCmd('^alter ^table %f ^drop ^constraint %i', cnt, cnt.constraintName); } + dropConstraint(cnt: ConstraintInfo) { + switch (cnt.constraintType) { + case 'primaryKey': + this.dropPrimaryKey(cnt as PrimaryKeyInfo); + break; + case 'foreignKey': + this.dropForeignKey(cnt as ForeignKeyInfo); + break; + case 'unique': + this.dropUnique(cnt as UniqueInfo); + break; + case 'check': + this.dropCheck(cnt as CheckInfo); + break; + case 'index': + this.dropIndex(cnt as IndexInfo); + break; + } + } dropForeignKey(fk: ForeignKeyInfo) { if (this.dialect.explicitDropConstraint) { this.putCmd('^alter ^table %f ^drop ^foreign ^key %i', fk, fk.constraintName); } else { - this.dropConstraint(fk); + this.dropConstraintCore(fk); } } createForeignKey(fk: ForeignKeyInfo) { @@ -369,7 +388,7 @@ export class SqlDumper implements AlterProcessor { if (this.dialect.explicitDropConstraint) { this.putCmd('^alter ^table %f ^drop ^primary ^key', pk); } else { - this.dropConstraint(pk); + this.dropConstraintCore(pk); } } createPrimaryKey(pk: PrimaryKeyInfo) { @@ -385,7 +404,7 @@ export class SqlDumper implements AlterProcessor { createIndex(ix: IndexInfo) {} dropUnique(uq: UniqueInfo) { - this.dropConstraint(uq); + this.dropConstraintCore(uq); } createUniqueCore(uq: UniqueInfo) { this.put( @@ -402,7 +421,7 @@ export class SqlDumper implements AlterProcessor { } dropCheck(ch: CheckInfo) { - this.dropConstraint(ch); + this.dropConstraintCore(ch); } createCheckCore(ch: CheckInfo) { diff --git a/packages/tools/src/alterPlan.ts b/packages/tools/src/alterPlan.ts index d1321b6e2..ad7b407e2 100644 --- a/packages/tools/src/alterPlan.ts +++ b/packages/tools/src/alterPlan.ts @@ -1,4 +1,13 @@ -import { AlterProcessor, ColumnInfo, ConstraintInfo, DatabaseInfo, NamedObjectInfo, TableInfo } from '../../types'; +import _ from 'lodash'; +import { + AlterProcessor, + ColumnInfo, + ConstraintInfo, + DatabaseInfo, + NamedObjectInfo, + SqlDialect, + TableInfo, +} from '../../types'; interface AlterOperation_CreateTable { operationType: 'createTable'; @@ -74,8 +83,8 @@ type AlterOperation = | AlterOperation_RenameConstraint; export class AlterPlan { - operations: AlterOperation[] = []; - constructor(public db: DatabaseInfo) {} + public operations: AlterOperation[] = []; + constructor(public db: DatabaseInfo, public dialect: SqlDialect) {} createTable(table: TableInfo) { this.operations.push({ @@ -164,6 +173,45 @@ export class AlterPlan { runAlterOperation(op, processor); } } + + _addLogicalDependencies(): AlterOperation[] { + const lists = this.operations.map(op => { + if (op.operationType == 'dropColumn') { + const table = this.db.tables.find( + x => x.pureName == op.oldObject.pureName && x.schemaName == op.oldObject.schemaName + ); + const deletedFks = this.dialect.dropColumnDependencies?.includes('foreignKey') + ? table.dependencies.filter(fk => fk.columns.find(col => col.refColumnName == op.oldObject.columnName)) + : []; + + const deletedConstraints = _.compact([ + table.primaryKey, + ...table.foreignKeys, + ...table.indexes, + ...table.uniques, + ]).filter(cnt => cnt.columns.find(col => col.columnName == op.oldObject.columnName)); + + const res: AlterOperation[] = [ + ...[...deletedFks, ...deletedConstraints].map(oldObject => { + const opRes: AlterOperation = { + operationType: 'dropConstraint', + oldObject, + }; + return opRes; + }), + op, + ]; + return res; + } + return [op]; + }); + + return _.flatten(lists); + } + + transformPlan() { + this.operations = this._addLogicalDependencies(); + } } export function runAlterOperation(op: AlterOperation, processor: AlterProcessor) { diff --git a/packages/tools/src/diffTools.ts b/packages/tools/src/diffTools.ts index f1f8944b5..5cdc737fb 100644 --- a/packages/tools/src/diffTools.ts +++ b/packages/tools/src/diffTools.ts @@ -272,14 +272,16 @@ export function createAlterTablePlan( oldTable: TableInfo, newTable: TableInfo, opts: DbDiffOptions, - db: DatabaseInfo + db: DatabaseInfo, + driver: EngineDriver ): AlterPlan { - const plan = new AlterPlan(db); + const plan = new AlterPlan(db, driver.dialect); if (oldTable == null) { plan.createTable(newTable); } else { planAlterTable(plan, oldTable, newTable, opts); } + plan.transformPlan(); return plan; } @@ -290,7 +292,7 @@ export function getAlterTableScript( db: DatabaseInfo, driver: EngineDriver ): string { - const plan = createAlterTablePlan(oldTable, newTable, opts, db); + const plan = createAlterTablePlan(oldTable, newTable, opts, db, driver); const dmp = driver.createDumper(); plan.run(dmp); return dmp.s; diff --git a/packages/types/alter-processor.d.ts b/packages/types/alter-processor.d.ts index 5d61162dc..849fbe0a1 100644 --- a/packages/types/alter-processor.d.ts +++ b/packages/types/alter-processor.d.ts @@ -4,7 +4,7 @@ export interface AlterProcessor { createTable(table: TableInfo); dropTable(table: TableInfo); createColumn(column: ColumnInfo, constraints: ConstraintInfo[]); - changeColumn(oldColumn: ColumnInfo, newColumn: ColumnInfo, constraints: ConstraintInfo[]); + changeColumn(oldColumn: ColumnInfo, newColumn: ColumnInfo, constraints?: ConstraintInfo[]); dropColumn(column: ColumnInfo); createConstraint(constraint: ConstraintInfo); changeConstraint(oldConstraint: ConstraintInfo, newConstraint: ConstraintInfo); diff --git a/packages/types/dialect.d.ts b/packages/types/dialect.d.ts index c7fbd678a..aa0985304 100644 --- a/packages/types/dialect.d.ts +++ b/packages/types/dialect.d.ts @@ -10,5 +10,7 @@ export interface SqlDialect { explicitDropConstraint?: boolean; anonymousPrimaryKey?: boolean; enableConstraintsPerTable?: boolean; + dropColumnDependencies?: string[]; + changeColumnDependencies?: string[]; nosql?: boolean; // mongo } diff --git a/plugins/dbgate-plugin-mssql/src/frontend/driver.js b/plugins/dbgate-plugin-mssql/src/frontend/driver.js index 3acb51d05..e27421fb0 100644 --- a/plugins/dbgate-plugin-mssql/src/frontend/driver.js +++ b/plugins/dbgate-plugin-mssql/src/frontend/driver.js @@ -12,6 +12,8 @@ const dialect = { fallbackDataType: 'nvarchar(max)', explicitDropConstraint: false, enableConstraintsPerTable: true, + dropColumnDependencies: ['default', 'foreignKey', 'index'], + changeColumnDependencies: ['index'], anonymousPrimaryKey: false, quoteIdentifier(s) { return `[${s}]`; diff --git a/plugins/dbgate-plugin-postgres/src/backend/drivers.js b/plugins/dbgate-plugin-postgres/src/backend/drivers.js index a72a9125f..e96c44364 100644 --- a/plugins/dbgate-plugin-postgres/src/backend/drivers.js +++ b/plugins/dbgate-plugin-postgres/src/backend/drivers.js @@ -74,7 +74,7 @@ const drivers = driverBases.map(driverBase => ({ } const res = await client.query({ text: sql, rowMode: 'array' }); const columns = extractPostgresColumns(res); - return { rows: res.rows.map(row => zipDataRow(row, columns)), columns }; + return { rows: (res.rows || []).map(row => zipDataRow(row, columns)), columns }; }, stream(client, sql, options) { const query = new pg.Query({ diff --git a/plugins/dbgate-plugin-sqlite/src/frontend/driver.js b/plugins/dbgate-plugin-sqlite/src/frontend/driver.js index 99c7c1feb..55eef9773 100644 --- a/plugins/dbgate-plugin-sqlite/src/frontend/driver.js +++ b/plugins/dbgate-plugin-sqlite/src/frontend/driver.js @@ -14,8 +14,10 @@ const dialect = { limitSelect: true, rangeSelect: true, offsetFetchRangeSyntax: false, + explicitDropConstraint: true, stringEscapeChar: "'", fallbackDataType: 'nvarchar(max)', + dropColumnDependencies: ['index'], quoteIdentifier(s) { return `[${s}]`; },