fk deploy fix

This commit is contained in:
Jan Prochazka
2021-10-14 15:49:07 +02:00
parent 01681b9d87
commit cb64a43a78
6 changed files with 207 additions and 50 deletions

View File

@@ -11,14 +11,18 @@ function checkStructure(structure, model) {
const expected = databaseInfoFromYamlModel(model); const expected = databaseInfoFromYamlModel(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(
_.sortBy(structure.tables, 'pureName'),
_.sortBy(expected.tables, 'pureName')
)) {
expect(realTable.columns.length).toBeGreaterThanOrEqual(expectedTable.columns.length); expect(realTable.columns.length).toBeGreaterThanOrEqual(expectedTable.columns.length);
} }
} }
async function testDatabaseDeploy(conn, driver, dbModelsYaml) { async function testDatabaseDeploy(conn, driver, dbModelsYaml, testEmptyLastScript) {
let index = 0;
for (const loadedDbModel of dbModelsYaml) { for (const loadedDbModel of dbModelsYaml) {
const sql = await generateDeploySql({ const { sql, isEmpty } = await generateDeploySql({
systemConnection: conn, systemConnection: conn,
driver, driver,
loadedDbModel, loadedDbModel,
@@ -26,11 +30,18 @@ async function testDatabaseDeploy(conn, driver, dbModelsYaml) {
console.debug('Generated deploy script:', sql); console.debug('Generated deploy script:', sql);
expect(sql.toUpperCase().includes('DROP ')).toBeFalsy(); expect(sql.toUpperCase().includes('DROP ')).toBeFalsy();
console.log('dbModelsYaml.length', dbModelsYaml.length, index);
if (testEmptyLastScript && index == dbModelsYaml.length - 1) {
expect(isEmpty).toBeTruthy();
}
await deployDb({ await deployDb({
systemConnection: conn, systemConnection: conn,
driver, driver,
loadedDbModel, loadedDbModel,
}); });
index++;
} }
const structure = await driver.analyseFull(conn); const structure = await driver.analyseFull(conn);
@@ -59,28 +70,33 @@ describe('Deploy database', () => {
test.each(engines.map(engine => [engine.label, engine]))( test.each(engines.map(engine => [engine.label, engine]))(
'Deploy database simple twice - %s', 'Deploy database simple twice - %s',
testWrapper(async (conn, driver, engine) => { testWrapper(async (conn, driver, engine) => {
await testDatabaseDeploy(conn, driver, [ await testDatabaseDeploy(
conn,
driver,
[ [
{ [
name: 't1.table.yaml', {
json: { name: 't1.table.yaml',
name: 't1', json: {
columns: [{ name: 'id', type: 'int' }], name: 't1',
primaryKey: ['id'], columns: [{ name: 'id', type: 'int' }],
primaryKey: ['id'],
},
}, },
}, ],
], [
[ {
{ name: 't1.table.yaml',
name: 't1.table.yaml', json: {
json: { name: 't1',
name: 't1', columns: [{ name: 'id', type: 'int' }],
columns: [{ name: 'id', type: 'int' }], primaryKey: ['id'],
primaryKey: ['id'], },
}, },
}, ],
], ],
]); true
);
}) })
); );
@@ -118,31 +134,91 @@ describe('Deploy database', () => {
test.each(engines.map(engine => [engine.label, engine]))( test.each(engines.map(engine => [engine.label, engine]))(
'Dont drop column - %s', 'Dont drop column - %s',
testWrapper(async (conn, driver, engine) => { testWrapper(async (conn, driver, engine) => {
await testDatabaseDeploy(conn, driver, [ await testDatabaseDeploy(
conn,
driver,
[ [
{ [
name: 't1.table.yaml', {
json: { name: 't1.table.yaml',
name: 't1', json: {
columns: [ name: 't1',
{ name: 'id', type: 'int' }, columns: [
{ name: 'val', type: 'int' }, { name: 'id', type: 'int' },
], { name: 'val', type: 'int' },
primaryKey: ['id'], ],
primaryKey: ['id'],
},
}, },
}, ],
[
{
name: 't1.table.yaml',
json: {
name: 't1',
columns: [{ name: 'id', type: 'int' }],
primaryKey: ['id'],
},
},
],
], ],
true
);
})
);
test.each(engines.map(engine => [engine.label, engine]))(
'Foreign keys - %s',
testWrapper(async (conn, driver, engine) => {
await testDatabaseDeploy(
conn,
driver,
[ [
{ [
name: 't1.table.yaml', {
json: { name: 't2.table.yaml',
name: 't1', json: {
columns: [{ name: 'id', type: 'int' }], name: 't2',
primaryKey: ['id'], columns: [
{ name: 't2id', type: 'int' },
{ name: 't1id', type: 'int', references: 't1' },
],
primaryKey: ['t2id'],
},
}, },
}, {
name: 't1.table.yaml',
json: {
name: 't1',
columns: [{ name: 't1id', type: 'int' }],
primaryKey: ['t1id'],
},
},
],
[
{
name: 't2.table.yaml',
json: {
name: 't2',
columns: [
{ name: 't2id', type: 'int' },
{ name: 't1id', type: 'int', references: 't1' },
],
primaryKey: ['t2id'],
},
},
{
name: 't1.table.yaml',
json: {
name: 't1',
columns: [{ name: 't1id', type: 'int' }],
primaryKey: ['t1id'],
},
},
],
], ],
]); true
);
}) })
); );
}); });

View File

@@ -2,7 +2,7 @@ const generateDeploySql = require('./generateDeploySql');
const executeQuery = require('./executeQuery'); const executeQuery = require('./executeQuery');
async function deployDb({ connection, systemConnection, driver, analysedStructure, modelFolder, loadedDbModel }) { async function deployDb({ connection, systemConnection, driver, analysedStructure, modelFolder, loadedDbModel }) {
const sql = await generateDeploySql({ const { sql } = await generateDeploySql({
connection, connection,
systemConnection, systemConnection,
driver, driver,

View File

@@ -38,12 +38,14 @@ async function generateDeploySql({
noDropSqlObject: true, noDropSqlObject: true,
noRenameTable: true, noRenameTable: true,
noRenameColumn: true, noRenameColumn: true,
ignoreForeignKeyActions: true,
}; };
const currentModelPaired = matchPairedObjects(deployedModel, currentModel, opts); const currentModelPaired = matchPairedObjects(deployedModel, currentModel, opts);
// console.log('deployedModel', deployedModel.tables[0]);
// console.log('currentModel', currentModel.tables[0]); // console.log('currentModel', currentModel.tables[0]);
// console.log('currentModelPaired', currentModelPaired.tables[0]); // console.log('currentModelPaired', currentModelPaired.tables[0]);
const { sql } = getAlterDatabaseScript(currentModelPaired, deployedModel, opts, deployedModel, driver); const res = getAlterDatabaseScript(currentModelPaired, deployedModel, opts, deployedModel, driver);
return sql; return res;
} }
module.exports = generateDeploySql; module.exports = generateDeploySql;

View File

@@ -244,6 +244,10 @@ export class AlterPlan {
if (op.operationType == 'dropColumn') { if (op.operationType == 'dropColumn') {
const constraints = this._getDependendColumnConstraints(op.oldObject, this.dialect.dropColumnDependencies); const constraints = this._getDependendColumnConstraints(op.oldObject, this.dialect.dropColumnDependencies);
if (constraints.length > 0 && this.opts.noDropConstraint) {
return [];
}
const res: AlterOperation[] = [ const res: AlterOperation[] = [
...constraints.map(oldObject => { ...constraints.map(oldObject => {
const opRes: AlterOperation = { const opRes: AlterOperation = {
@@ -260,6 +264,10 @@ export class AlterPlan {
if (op.operationType == 'changeColumn') { if (op.operationType == 'changeColumn') {
const constraints = this._getDependendColumnConstraints(op.oldObject, this.dialect.changeColumnDependencies); const constraints = this._getDependendColumnConstraints(op.oldObject, this.dialect.changeColumnDependencies);
if (constraints.length > 0 && this.opts.noDropConstraint) {
return [];
}
const res: AlterOperation[] = [ const res: AlterOperation[] = [
...constraints.map(oldObject => { ...constraints.map(oldObject => {
const opRes: AlterOperation = { const opRes: AlterOperation = {
@@ -300,6 +308,11 @@ export class AlterPlan {
} }
if (op.operationType == 'changeConstraint') { if (op.operationType == 'changeConstraint') {
if (this.opts.noDropConstraint) {
// skip constraint recreate
return [];
}
this.recreates.constraints += 1; this.recreates.constraints += 1;
const opDrop: AlterOperation = { const opDrop: AlterOperation = {
operationType: 'dropConstraint', operationType: 'dropConstraint',
@@ -418,6 +431,37 @@ export class AlterPlan {
return res; return res;
} }
_moveForeignKeysToLast(): AlterOperation[] {
if (!this.dialect.createForeignKey) {
return this.operations;
}
const fks = [];
const res = this.operations.map(op => {
if (op.operationType == 'createTable') {
fks.push(...(op.newObject.foreignKeys || []));
return {
...op,
newObject: {
...op.newObject,
foreignKeys: [],
},
};
}
return op;
});
return [
...res,
...fks.map(
fk =>
({
operationType: 'createConstraint',
newObject: fk,
} as AlterOperation_CreateConstraint)
),
];
}
transformPlan() { transformPlan() {
// console.log('*****************OPERATIONS0', this.operations); // console.log('*****************OPERATIONS0', this.operations);
@@ -432,6 +476,10 @@ export class AlterPlan {
this.operations = this._groupTableRecreations(); this.operations = this._groupTableRecreations();
// console.log('*****************OPERATIONS3', this.operations); // console.log('*****************OPERATIONS3', this.operations);
this.operations = this._moveForeignKeysToLast();
// console.log('*****************OPERATIONS4', this.operations);
} }
} }

View File

@@ -7,10 +7,13 @@ import {
SqlDialect, SqlDialect,
TableInfo, TableInfo,
} from 'dbgate-types'; } from 'dbgate-types';
import _ from 'lodash';
import uuidv1 from 'uuid/v1'; import uuidv1 from 'uuid/v1';
import { AlterPlan } from './alterPlan'; import { AlterPlan } from './alterPlan';
import stableStringify from 'json-stable-stringify'; import stableStringify from 'json-stable-stringify';
import _omit from 'lodash/omit';
import _cloneDeep from 'lodash/cloneDeep';
import _isEqual from 'lodash/isEqual';
import _pick from 'lodash/pick';
type DbDiffSchemaMode = 'strict' | 'ignore' | 'ignoreImplicit'; type DbDiffSchemaMode = 'strict' | 'ignore' | 'ignoreImplicit';
@@ -32,6 +35,7 @@ export interface DbDiffOptions {
noDropSqlObject?: boolean; noDropSqlObject?: boolean;
noRenameTable?: boolean; noRenameTable?: boolean;
noRenameColumn?: boolean; noRenameColumn?: boolean;
ignoreForeignKeyActions?: boolean;
} }
export function generateTablePairingId(table: TableInfo): TableInfo { export function generateTablePairingId(table: TableInfo): TableInfo {
@@ -245,15 +249,29 @@ export function testEqualColumns(
function testEqualConstraints(a: ConstraintInfo, b: ConstraintInfo, opts: DbDiffOptions = {}) { function testEqualConstraints(a: ConstraintInfo, b: ConstraintInfo, opts: DbDiffOptions = {}) {
const omitList = []; const omitList = [];
if (opts.ignoreConstraintNames) omitList.push('constraintName'); if (opts.ignoreForeignKeyActions) {
if (opts.schemaMode == 'ignore') omitList.push('schemaName'); omitList.push('updateAction');
omitList.push('deleteAction');
}
if (opts.ignoreConstraintNames) {
omitList.push('constraintName');
}
if (opts.schemaMode == 'ignore') {
omitList.push('schemaName');
omitList.push('refSchemaName');
}
// if (a.constraintType == 'primaryKey' && b.constraintType == 'primaryKey') { // if (a.constraintType == 'primaryKey' && b.constraintType == 'primaryKey') {
// console.log('PK1', stableStringify(_.omit(a, omitList))); // console.log('PK1', stableStringify(_.omit(a, omitList)));
// console.log('PK2', stableStringify(_.omit(b, omitList))); // console.log('PK2', stableStringify(_.omit(b, omitList)));
// } // }
return stableStringify(_.omit(a, omitList)) == stableStringify(_.omit(b, omitList)); // if (a.constraintType == 'foreignKey' && b.constraintType == 'foreignKey') {
// console.log('FK1', stableStringify(_omit(a, omitList)));
// console.log('FK2', stableStringify(_omit(b, omitList)));
// }
return stableStringify(_omit(a, omitList)) == stableStringify(_omit(b, omitList));
} }
export function testEqualTypes(a: ColumnInfo, b: ColumnInfo, opts: DbDiffOptions = {}) { export function testEqualTypes(a: ColumnInfo, b: ColumnInfo, opts: DbDiffOptions = {}) {
@@ -441,11 +459,12 @@ export function getAlterDatabaseScript(
return { return {
sql: dmp.s, sql: dmp.s,
recreates: plan.recreates, recreates: plan.recreates,
isEmpty: plan.operations.length == 0,
}; };
} }
export function matchPairedObjects(db1: DatabaseInfo, db2: DatabaseInfo, opts: DbDiffOptions) { 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] || []) {
@@ -458,6 +477,18 @@ export function matchPairedObjects(db1: DatabaseInfo, db2: DatabaseInfo, opts: D
const col1 = obj1.columns.find(x => testEqualNames(x.columnName, col2.columnName, opts)); const col1 = obj1.columns.find(x => testEqualNames(x.columnName, col2.columnName, opts));
if (col1) col2.pairingId = col1.pairingId; if (col1) col2.pairingId = col1.pairingId;
} }
for (const fk2 of obj2.foreignKeys) {
const fk1 = obj1.foreignKeys.find(
x =>
testEqualNames(x.refTableName, fk2.refTableName, opts) &&
_isEqual(
x.columns.map(y => _pick(y, ['columnName', 'refColumnName'])),
fk2.columns.map(y => _pick(y, ['columnName', 'refColumnName']))
)
);
if (fk1) fk2.pairingId = fk1.pairingId;
}
} }
} }
} }

View File

@@ -43,7 +43,7 @@
archiveFolder: data.name, archiveFolder: data.name,
}); });
newQuery({ initialData: resp.data }); newQuery({ initialData: resp.data.sql });
}; };
function createMenu() { function createMenu() {