alter table WIP

This commit is contained in:
Jan Prochazka
2021-08-26 11:45:44 +02:00
parent a5b5f36298
commit 3fe13f0443
9 changed files with 98 additions and 17 deletions

View File

@@ -24,16 +24,20 @@ async function testTableDiff(conn, driver, mangle) {
await driver.query( await driver.query(
conn, conn,
`create table t1 ( `create table t1 (
id int not null primary key, col_pk int not null primary key,
col_std int null, col_std int null,
col_def int null default 12, col_def int null default 12,
col_fk int null references t0(id), 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 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 tget = x => x.tables.find(y => y.pureName == 't1');
const structure1 = generateDbPairingId(extendDatabaseInfo(await driver.analyseFull(conn))); const structure1 = generateDbPairingId(extendDatabaseInfo(await driver.analyseFull(conn)));
let structure2 = _.cloneDeep(structure1); let structure2 = _.cloneDeep(structure1);
@@ -51,8 +55,10 @@ async function testTableDiff(conn, driver, mangle) {
// expect(stableStringify(structure2)).toEqual(stableStringify(structure2Real)); // expect(stableStringify(structure2)).toEqual(stableStringify(structure2Real));
} }
// const TESTED_COLUMNS = ['col_std', 'col_def', 'col_fk', 'col_idx']; const TESTED_COLUMNS = ['col_pk', 'col_std', 'col_def', 'col_fk', 'col_ref', 'col_idx', 'col_uq'];
const TESTED_COLUMNS = ['col_std']; // const TESTED_COLUMNS = ['col_idx'];
// const TESTED_COLUMNS = ['col_fk'];
// const TESTED_COLUMNS = ['col_std'];
function engines_columns_source() { function engines_columns_source() {
return _.flatten(engines.map(engine => TESTED_COLUMNS.map(column => [engine.label, column, engine]))); return _.flatten(engines.map(engine => TESTED_COLUMNS.map(column => [engine.label, column, engine])));

View File

@@ -350,14 +350,33 @@ export class SqlDumper implements AlterProcessor {
changeTriggerSchema(obj: TriggerInfo, newSchema: string) {} changeTriggerSchema(obj: TriggerInfo, newSchema: string) {}
renameTrigger(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); 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) { dropForeignKey(fk: ForeignKeyInfo) {
if (this.dialect.explicitDropConstraint) { if (this.dialect.explicitDropConstraint) {
this.putCmd('^alter ^table %f ^drop ^foreign ^key %i', fk, fk.constraintName); this.putCmd('^alter ^table %f ^drop ^foreign ^key %i', fk, fk.constraintName);
} else { } else {
this.dropConstraint(fk); this.dropConstraintCore(fk);
} }
} }
createForeignKey(fk: ForeignKeyInfo) { createForeignKey(fk: ForeignKeyInfo) {
@@ -369,7 +388,7 @@ export class SqlDumper implements AlterProcessor {
if (this.dialect.explicitDropConstraint) { if (this.dialect.explicitDropConstraint) {
this.putCmd('^alter ^table %f ^drop ^primary ^key', pk); this.putCmd('^alter ^table %f ^drop ^primary ^key', pk);
} else { } else {
this.dropConstraint(pk); this.dropConstraintCore(pk);
} }
} }
createPrimaryKey(pk: PrimaryKeyInfo) { createPrimaryKey(pk: PrimaryKeyInfo) {
@@ -385,7 +404,7 @@ export class SqlDumper implements AlterProcessor {
createIndex(ix: IndexInfo) {} createIndex(ix: IndexInfo) {}
dropUnique(uq: UniqueInfo) { dropUnique(uq: UniqueInfo) {
this.dropConstraint(uq); this.dropConstraintCore(uq);
} }
createUniqueCore(uq: UniqueInfo) { createUniqueCore(uq: UniqueInfo) {
this.put( this.put(
@@ -402,7 +421,7 @@ export class SqlDumper implements AlterProcessor {
} }
dropCheck(ch: CheckInfo) { dropCheck(ch: CheckInfo) {
this.dropConstraint(ch); this.dropConstraintCore(ch);
} }
createCheckCore(ch: CheckInfo) { createCheckCore(ch: CheckInfo) {

View File

@@ -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 { interface AlterOperation_CreateTable {
operationType: 'createTable'; operationType: 'createTable';
@@ -74,8 +83,8 @@ type AlterOperation =
| AlterOperation_RenameConstraint; | AlterOperation_RenameConstraint;
export class AlterPlan { export class AlterPlan {
operations: AlterOperation[] = []; public operations: AlterOperation[] = [];
constructor(public db: DatabaseInfo) {} constructor(public db: DatabaseInfo, public dialect: SqlDialect) {}
createTable(table: TableInfo) { createTable(table: TableInfo) {
this.operations.push({ this.operations.push({
@@ -164,6 +173,45 @@ export class AlterPlan {
runAlterOperation(op, processor); 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) { export function runAlterOperation(op: AlterOperation, processor: AlterProcessor) {

View File

@@ -272,14 +272,16 @@ export function createAlterTablePlan(
oldTable: TableInfo, oldTable: TableInfo,
newTable: TableInfo, newTable: TableInfo,
opts: DbDiffOptions, opts: DbDiffOptions,
db: DatabaseInfo db: DatabaseInfo,
driver: EngineDriver
): AlterPlan { ): AlterPlan {
const plan = new AlterPlan(db); const plan = new AlterPlan(db, driver.dialect);
if (oldTable == null) { if (oldTable == null) {
plan.createTable(newTable); plan.createTable(newTable);
} else { } else {
planAlterTable(plan, oldTable, newTable, opts); planAlterTable(plan, oldTable, newTable, opts);
} }
plan.transformPlan();
return plan; return plan;
} }
@@ -290,7 +292,7 @@ export function getAlterTableScript(
db: DatabaseInfo, db: DatabaseInfo,
driver: EngineDriver driver: EngineDriver
): string { ): string {
const plan = createAlterTablePlan(oldTable, newTable, opts, db); const plan = createAlterTablePlan(oldTable, newTable, opts, db, driver);
const dmp = driver.createDumper(); const dmp = driver.createDumper();
plan.run(dmp); plan.run(dmp);
return dmp.s; return dmp.s;

View File

@@ -4,7 +4,7 @@ export interface AlterProcessor {
createTable(table: TableInfo); createTable(table: TableInfo);
dropTable(table: TableInfo); dropTable(table: TableInfo);
createColumn(column: ColumnInfo, constraints: ConstraintInfo[]); createColumn(column: ColumnInfo, constraints: ConstraintInfo[]);
changeColumn(oldColumn: ColumnInfo, newColumn: ColumnInfo, constraints: ConstraintInfo[]); changeColumn(oldColumn: ColumnInfo, newColumn: ColumnInfo, constraints?: ConstraintInfo[]);
dropColumn(column: ColumnInfo); dropColumn(column: ColumnInfo);
createConstraint(constraint: ConstraintInfo); createConstraint(constraint: ConstraintInfo);
changeConstraint(oldConstraint: ConstraintInfo, newConstraint: ConstraintInfo); changeConstraint(oldConstraint: ConstraintInfo, newConstraint: ConstraintInfo);

View File

@@ -10,5 +10,7 @@ export interface SqlDialect {
explicitDropConstraint?: boolean; explicitDropConstraint?: boolean;
anonymousPrimaryKey?: boolean; anonymousPrimaryKey?: boolean;
enableConstraintsPerTable?: boolean; enableConstraintsPerTable?: boolean;
dropColumnDependencies?: string[];
changeColumnDependencies?: string[];
nosql?: boolean; // mongo nosql?: boolean; // mongo
} }

View File

@@ -12,6 +12,8 @@ const dialect = {
fallbackDataType: 'nvarchar(max)', fallbackDataType: 'nvarchar(max)',
explicitDropConstraint: false, explicitDropConstraint: false,
enableConstraintsPerTable: true, enableConstraintsPerTable: true,
dropColumnDependencies: ['default', 'foreignKey', 'index'],
changeColumnDependencies: ['index'],
anonymousPrimaryKey: false, anonymousPrimaryKey: false,
quoteIdentifier(s) { quoteIdentifier(s) {
return `[${s}]`; return `[${s}]`;

View File

@@ -74,7 +74,7 @@ const drivers = driverBases.map(driverBase => ({
} }
const res = await client.query({ text: sql, rowMode: 'array' }); const res = await client.query({ text: sql, rowMode: 'array' });
const columns = extractPostgresColumns(res); 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) { stream(client, sql, options) {
const query = new pg.Query({ const query = new pg.Query({

View File

@@ -14,8 +14,10 @@ const dialect = {
limitSelect: true, limitSelect: true,
rangeSelect: true, rangeSelect: true,
offsetFetchRangeSyntax: false, offsetFetchRangeSyntax: false,
explicitDropConstraint: true,
stringEscapeChar: "'", stringEscapeChar: "'",
fallbackDataType: 'nvarchar(max)', fallbackDataType: 'nvarchar(max)',
dropColumnDependencies: ['index'],
quoteIdentifier(s) { quoteIdentifier(s) {
return `[${s}]`; return `[${s}]`;
}, },