mirror of
https://github.com/DeNNiiInc/dbgate.git
synced 2026-04-29 02:56:01 +00:00
fk deploy fix
This commit is contained in:
@@ -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
|
||||||
|
);
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,7 +43,7 @@
|
|||||||
archiveFolder: data.name,
|
archiveFolder: data.name,
|
||||||
});
|
});
|
||||||
|
|
||||||
newQuery({ initialData: resp.data });
|
newQuery({ initialData: resp.data.sql });
|
||||||
};
|
};
|
||||||
|
|
||||||
function createMenu() {
|
function createMenu() {
|
||||||
|
|||||||
Reference in New Issue
Block a user