mirror of
https://github.com/dbgate/dbgate
synced 2024-11-07 20:26:23 +00:00
deploy db diff options
This commit is contained in:
parent
aeafa81cb2
commit
fa2bb52007
@ -12,7 +12,7 @@ function checkStructure(structure, model) {
|
|||||||
expect(structure.tables.length).toEqual(expected.tables.length);
|
expect(structure.tables.length).toEqual(expected.tables.length);
|
||||||
|
|
||||||
for (const [realTable, expectedTable] of _.zip(structure.tables, expected.tables)) {
|
for (const [realTable, expectedTable] of _.zip(structure.tables, expected.tables)) {
|
||||||
expect(realTable.columns.length).toEqual(expectedTable.columns.length);
|
expect(realTable.columns.length).toBeGreaterThanOrEqual(expectedTable.columns.length);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -83,4 +83,66 @@ describe('Deploy database', () => {
|
|||||||
]);
|
]);
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
test.each(engines.map(engine => [engine.label, engine]))(
|
||||||
|
'Add column - %s',
|
||||||
|
testWrapper(async (conn, driver, engine) => {
|
||||||
|
await testDatabaseDeploy(conn, driver, [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
name: 't1.table.yaml',
|
||||||
|
json: {
|
||||||
|
name: 't1',
|
||||||
|
columns: [{ name: 'id', type: 'int' }],
|
||||||
|
primaryKey: ['id'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
name: 't1.table.yaml',
|
||||||
|
json: {
|
||||||
|
name: 't1',
|
||||||
|
columns: [
|
||||||
|
{ name: 'id', type: 'int' },
|
||||||
|
{ name: 'val', type: 'int' },
|
||||||
|
],
|
||||||
|
primaryKey: ['id'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
test.each(engines.map(engine => [engine.label, engine]))(
|
||||||
|
'Dont drop column - %s',
|
||||||
|
testWrapper(async (conn, driver, engine) => {
|
||||||
|
await testDatabaseDeploy(conn, driver, [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
name: 't1.table.yaml',
|
||||||
|
json: {
|
||||||
|
name: 't1',
|
||||||
|
columns: [
|
||||||
|
{ name: 'id', type: 'int' },
|
||||||
|
{ name: 'val', type: 'int' },
|
||||||
|
],
|
||||||
|
primaryKey: ['id'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
name: 't1.table.yaml',
|
||||||
|
json: {
|
||||||
|
name: 't1',
|
||||||
|
columns: [{ name: 'id', type: 'int' }],
|
||||||
|
primaryKey: ['id'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
})
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
@ -27,8 +27,22 @@ async function generateDeploySql({
|
|||||||
extendDatabaseInfo(loadedDbModel ? databaseInfoFromYamlModel(loadedDbModel) : await importDbModel(modelFolder))
|
extendDatabaseInfo(loadedDbModel ? databaseInfoFromYamlModel(loadedDbModel) : await importDbModel(modelFolder))
|
||||||
);
|
);
|
||||||
const currentModel = generateDbPairingId(extendDatabaseInfo(analysedStructure));
|
const currentModel = generateDbPairingId(extendDatabaseInfo(analysedStructure));
|
||||||
const currentModelPaired = matchPairedObjects(deployedModel, currentModel);
|
const opts = {
|
||||||
const { sql } = getAlterDatabaseScript(currentModelPaired, deployedModel, {}, deployedModel, driver);
|
ignoreCase: true,
|
||||||
|
schemaMode: 'ignore',
|
||||||
|
ignoreConstraintNames: true,
|
||||||
|
|
||||||
|
noDropTable: true,
|
||||||
|
noDropColumn: true,
|
||||||
|
noDropConstraint: true,
|
||||||
|
noDropSqlObject: true,
|
||||||
|
noRenameTable: true,
|
||||||
|
noRenameColumn: true,
|
||||||
|
};
|
||||||
|
const currentModelPaired = matchPairedObjects(deployedModel, currentModel, opts);
|
||||||
|
// console.log('currentModel', currentModel.tables[0]);
|
||||||
|
// console.log('currentModelPaired', currentModelPaired.tables[0]);
|
||||||
|
const { sql } = getAlterDatabaseScript(currentModelPaired, deployedModel, opts, deployedModel, driver);
|
||||||
return sql;
|
return sql;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import { generateTablePairingId } from '.';
|
import { DbDiffOptions, generateTablePairingId } from './diffTools';
|
||||||
import {
|
import {
|
||||||
AlterProcessor,
|
AlterProcessor,
|
||||||
ColumnInfo,
|
ColumnInfo,
|
||||||
@ -111,7 +111,7 @@ export class AlterPlan {
|
|||||||
};
|
};
|
||||||
|
|
||||||
public operations: AlterOperation[] = [];
|
public operations: AlterOperation[] = [];
|
||||||
constructor(public db: DatabaseInfo, public dialect: SqlDialect) {}
|
constructor(public db: DatabaseInfo, public dialect: SqlDialect, public opts: DbDiffOptions) {}
|
||||||
|
|
||||||
createTable(table: TableInfo) {
|
createTable(table: TableInfo) {
|
||||||
this.operations.push({
|
this.operations.push({
|
||||||
@ -365,6 +365,12 @@ export class AlterPlan {
|
|||||||
|
|
||||||
// console.log('*****************RECREATED NEEDED', op, operationType, isAllowed);
|
// console.log('*****************RECREATED NEEDED', op, operationType, isAllowed);
|
||||||
// console.log(this.dialect);
|
// console.log(this.dialect);
|
||||||
|
|
||||||
|
if (this.opts.noDropTable) {
|
||||||
|
// skip this operation, as it cannot be achieved
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
const table = this.db.tables.find(
|
const table = this.db.tables.find(
|
||||||
x => x.pureName == op[objectField].pureName && x.schemaName == op[objectField].schemaName
|
x => x.pureName == op[objectField].pureName && x.schemaName == op[objectField].schemaName
|
||||||
);
|
);
|
||||||
|
@ -15,15 +15,23 @@ import stableStringify from 'json-stable-stringify';
|
|||||||
type DbDiffSchemaMode = 'strict' | 'ignore' | 'ignoreImplicit';
|
type DbDiffSchemaMode = 'strict' | 'ignore' | 'ignoreImplicit';
|
||||||
|
|
||||||
export interface DbDiffOptions {
|
export interface DbDiffOptions {
|
||||||
allowRecreateTable?: boolean;
|
// allowRecreateTable?: boolean;
|
||||||
allowRecreateConstraint?: boolean;
|
// allowRecreateConstraint?: boolean;
|
||||||
allowRecreateSpecificObject?: boolean;
|
// allowRecreateSpecificObject?: boolean;
|
||||||
allowPairRenamedTables?: boolean;
|
// allowPairRenamedTables?: boolean;
|
||||||
|
|
||||||
ignoreCase?: boolean;
|
ignoreCase?: boolean;
|
||||||
schemaMode?: DbDiffSchemaMode;
|
schemaMode?: DbDiffSchemaMode;
|
||||||
leftImplicitSchema?: string;
|
leftImplicitSchema?: string;
|
||||||
rightImplicitSchema?: string;
|
rightImplicitSchema?: string;
|
||||||
|
ignoreConstraintNames?: boolean;
|
||||||
|
|
||||||
|
noDropTable?: boolean;
|
||||||
|
noDropColumn?: boolean;
|
||||||
|
noDropConstraint?: boolean;
|
||||||
|
noDropSqlObject?: boolean;
|
||||||
|
noRenameTable?: boolean;
|
||||||
|
noRenameColumn?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function generateTablePairingId(table: TableInfo): TableInfo {
|
export function generateTablePairingId(table: TableInfo): TableInfo {
|
||||||
@ -82,7 +90,7 @@ export function generateDbPairingId(db: DatabaseInfo): DatabaseInfo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function testEqualNames(a: string, b: string, opts: DbDiffOptions) {
|
function testEqualNames(a: string, b: string, opts: DbDiffOptions) {
|
||||||
if (opts.ignoreCase) return a.toLowerCase() == b.toLowerCase();
|
if (opts.ignoreCase) return (a || '').toLowerCase() == (b || '').toLowerCase();
|
||||||
return a == b;
|
return a == b;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -236,10 +244,14 @@ export function testEqualColumns(
|
|||||||
}
|
}
|
||||||
|
|
||||||
function testEqualConstraints(a: ConstraintInfo, b: ConstraintInfo, opts: DbDiffOptions = {}) {
|
function testEqualConstraints(a: ConstraintInfo, b: ConstraintInfo, opts: DbDiffOptions = {}) {
|
||||||
if (a.constraintType=='primaryKey' && b.constraintType=='primaryKey') {
|
// if (a.constraintType == 'primaryKey' && b.constraintType == 'primaryKey') {
|
||||||
|
// console.log('PK1', stableStringify(opts.ignoreConstraintNames ? _.omit(a, ['constraintName']) : a));
|
||||||
}
|
// console.log('PK2', stableStringify(opts.ignoreConstraintNames ? _.omit(b, ['constraintName']) : b));
|
||||||
return stableStringify(a) == stableStringify(b);
|
// }
|
||||||
|
return (
|
||||||
|
stableStringify(opts.ignoreConstraintNames ? _.omit(a, ['constraintName']) : a) ==
|
||||||
|
stableStringify(opts.ignoreConstraintNames ? _.omit(b, ['constraintName']) : b)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function testEqualTypes(a: ColumnInfo, b: ColumnInfo, opts: DbDiffOptions = {}) {
|
export function testEqualTypes(a: ColumnInfo, b: ColumnInfo, opts: DbDiffOptions = {}) {
|
||||||
@ -291,10 +303,14 @@ function planAlterTable(plan: AlterPlan, oldTable: TableInfo, newTable: TableInf
|
|||||||
(a, b) => a.constraintType == 'primaryKey' && b.constraintType == 'primaryKey'
|
(a, b) => a.constraintType == 'primaryKey' && b.constraintType == 'primaryKey'
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (!opts.noDropConstraint) {
|
||||||
constraintPairs.filter(x => x[1] == null).forEach(x => plan.dropConstraint(x[0]));
|
constraintPairs.filter(x => x[1] == null).forEach(x => plan.dropConstraint(x[0]));
|
||||||
|
}
|
||||||
|
if (!opts.noDropColumn) {
|
||||||
columnPairs.filter(x => x[1] == null).forEach(x => plan.dropColumn(x[0]));
|
columnPairs.filter(x => x[1] == null).forEach(x => plan.dropColumn(x[0]));
|
||||||
|
}
|
||||||
|
|
||||||
if (!testEqualFullNames(oldTable, newTable, opts)) {
|
if (!testEqualFullNames(oldTable, newTable, opts) && !opts.noRenameTable) {
|
||||||
plan.renameTable(oldTable, newTable.pureName);
|
plan.renameTable(oldTable, newTable.pureName);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -304,7 +320,7 @@ function planAlterTable(plan: AlterPlan, oldTable: TableInfo, newTable: TableInf
|
|||||||
.filter(x => x[0] && x[1])
|
.filter(x => x[0] && x[1])
|
||||||
.forEach(x => {
|
.forEach(x => {
|
||||||
if (!testEqualColumns(x[0], x[1], true, true, opts)) {
|
if (!testEqualColumns(x[0], x[1], true, true, opts)) {
|
||||||
if (testEqualColumns(x[0], x[1], false, true, opts)) {
|
if (testEqualColumns(x[0], x[1], false, true, opts) && !opts.noRenameColumn) {
|
||||||
// console.log('PLAN RENAME COLUMN')
|
// console.log('PLAN RENAME COLUMN')
|
||||||
plan.renameColumn(x[0], x[1].columnName);
|
plan.renameColumn(x[0], x[1].columnName);
|
||||||
} else {
|
} else {
|
||||||
@ -333,7 +349,7 @@ export function createAlterTablePlan(
|
|||||||
db: DatabaseInfo,
|
db: DatabaseInfo,
|
||||||
driver: EngineDriver
|
driver: EngineDriver
|
||||||
): AlterPlan {
|
): AlterPlan {
|
||||||
const plan = new AlterPlan(db, driver.dialect);
|
const plan = new AlterPlan(db, driver.dialect, opts);
|
||||||
if (oldTable == null) {
|
if (oldTable == null) {
|
||||||
plan.createTable(newTable);
|
plan.createTable(newTable);
|
||||||
} else {
|
} else {
|
||||||
@ -350,19 +366,29 @@ export function createAlterDatabasePlan(
|
|||||||
db: DatabaseInfo,
|
db: DatabaseInfo,
|
||||||
driver: EngineDriver
|
driver: EngineDriver
|
||||||
): AlterPlan {
|
): AlterPlan {
|
||||||
const plan = new AlterPlan(db, driver.dialect);
|
const plan = new AlterPlan(db, driver.dialect, opts);
|
||||||
|
|
||||||
for (const objectTypeField of ['tables', 'views', 'procedures', 'matviews', 'functions']) {
|
for (const objectTypeField of ['tables', 'views', 'procedures', 'matviews', 'functions']) {
|
||||||
for (const oldobj of oldDb[objectTypeField] || []) {
|
for (const oldobj of oldDb[objectTypeField] || []) {
|
||||||
const newobj = (newDb[objectTypeField] || []).find(x => x.pairingId == oldobj.pairingId);
|
const newobj = (newDb[objectTypeField] || []).find(x => x.pairingId == oldobj.pairingId);
|
||||||
if (objectTypeField == 'tables') {
|
if (objectTypeField == 'tables') {
|
||||||
if (newobj == null) plan.dropTable(oldobj);
|
if (newobj == null) {
|
||||||
else planAlterTable(plan, oldobj, newobj, opts);
|
if (!opts.noDropTable) {
|
||||||
|
plan.dropTable(oldobj);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
if (newobj == null) plan.dropSqlObject(oldobj);
|
planAlterTable(plan, oldobj, newobj, opts);
|
||||||
else if (newobj.createSql != oldobj.createSql) {
|
}
|
||||||
plan.recreates.sqlObjects += 1;
|
} else {
|
||||||
|
if (newobj == null) {
|
||||||
|
if (!opts.noDropSqlObject) {
|
||||||
plan.dropSqlObject(oldobj);
|
plan.dropSqlObject(oldobj);
|
||||||
|
}
|
||||||
|
} else if (newobj.createSql != oldobj.createSql) {
|
||||||
|
plan.recreates.sqlObjects += 1;
|
||||||
|
if (!opts.noDropSqlObject) {
|
||||||
|
plan.dropSqlObject(oldobj);
|
||||||
|
}
|
||||||
plan.createSqlObject(newobj);
|
plan.createSqlObject(newobj);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -416,18 +442,18 @@ export function getAlterDatabaseScript(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function matchPairedObjects(db1: DatabaseInfo, db2: DatabaseInfo) {
|
export function matchPairedObjects(db1: DatabaseInfo, db2: DatabaseInfo, opts: DbDiffOptions) {
|
||||||
const res = _.cloneDeep(db2);
|
const res = _.cloneDeep(db2);
|
||||||
|
|
||||||
for (const objectTypeField of ['tables', 'views', 'procedures', 'matviews', 'functions']) {
|
for (const objectTypeField of ['tables', 'views', 'procedures', 'matviews', 'functions']) {
|
||||||
for (const obj2 of res[objectTypeField] || []) {
|
for (const obj2 of res[objectTypeField] || []) {
|
||||||
const obj1 = db1[objectTypeField].find(x => x.pureName == obj2.pureName);
|
const obj1 = db1[objectTypeField].find(x => testEqualFullNames(x, obj2, opts));
|
||||||
if (obj1) {
|
if (obj1) {
|
||||||
obj2.pairingId = obj1.pairingId;
|
obj2.pairingId = obj1.pairingId;
|
||||||
|
|
||||||
if (objectTypeField == 'tables') {
|
if (objectTypeField == 'tables') {
|
||||||
for (const col2 of obj2.columns) {
|
for (const col2 of obj2.columns) {
|
||||||
const col1 = obj1.columns.find(x => x.columnName == col2.columnName);
|
const col1 = obj1.columns.find(x => testEqualNames(x.columnName, col2.columnName, opts));
|
||||||
if (col1) col2.pairingId = col1.pairingId;
|
if (col1) col2.pairingId = col1.pairingId;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user