dbdeploy: undelete table works

This commit is contained in:
Jan Prochazka
2024-10-31 14:03:44 +01:00
parent d905962298
commit 5d4d2a447a
2 changed files with 125 additions and 7 deletions

View File

@@ -503,6 +503,7 @@ describe('Deploy database', () => {
}, },
], ],
[], [],
[],
], ],
{ {
dbdiffOptionsExtra: { dbdiffOptionsExtra: {
@@ -510,6 +511,7 @@ describe('Deploy database', () => {
deletedColumnPrefix: '_deleted_', deletedColumnPrefix: '_deleted_',
deletedSqlObjectPrefix: '_deleted_', deletedSqlObjectPrefix: '_deleted_',
}, },
disallowExtraObjects: true,
finalCheckAgainstModel: [ finalCheckAgainstModel: [
{ {
name: 't1.table.yaml', name: 't1.table.yaml',
@@ -573,6 +575,7 @@ describe('Deploy database', () => {
deletedColumnPrefix: '_deleted_', deletedColumnPrefix: '_deleted_',
deletedSqlObjectPrefix: '_deleted_', deletedSqlObjectPrefix: '_deleted_',
}, },
disallowExtraObjects: true,
finalCheckAgainstModel: [ finalCheckAgainstModel: [
{ {
name: 't1.table.yaml', name: 't1.table.yaml',
@@ -633,6 +636,7 @@ describe('Deploy database', () => {
deletedColumnPrefix: '_deleted_', deletedColumnPrefix: '_deleted_',
deletedSqlObjectPrefix: '_deleted_', deletedSqlObjectPrefix: '_deleted_',
}, },
disallowExtraObjects: true,
finalCheckAgainstModel: [ finalCheckAgainstModel: [
{ {
name: 't1.table.yaml', name: 't1.table.yaml',
@@ -650,4 +654,67 @@ describe('Deploy database', () => {
); );
}) })
); );
test.each(engines.map(engine => [engine.label, engine]))(
'Undelete table - %s',
testWrapper(async (conn, driver, engine) => {
await testDatabaseDeploy(
engine,
conn,
driver,
[
[
{
name: 't1.table.yaml',
json: {
name: 't1',
columns: [
{ name: 'id', type: 'int' },
{ name: 'val', type: 'int' },
],
primaryKey: ['id'],
},
},
],
// delete table
[],
// undelete table
[
{
name: 't1.table.yaml',
json: {
name: 't1',
columns: [
{ name: 'id', type: 'int' },
{ name: 'val', type: 'int' },
],
primaryKey: ['id'],
},
},
],
],
{
dbdiffOptionsExtra: {
deletedTablePrefix: '_deleted_',
deletedColumnPrefix: '_deleted_',
deletedSqlObjectPrefix: '_deleted_',
},
disallowExtraObjects: true,
finalCheckAgainstModel: [
{
name: 't1.table.yaml',
json: {
name: 't1',
columns: [
{ name: 'id', type: 'int' },
{ name: 'val', type: 'int' },
],
primaryKey: ['id'],
},
},
],
}
);
})
);
}); });

View File

@@ -144,7 +144,36 @@ export function generateDbPairingId(db: DatabaseInfo): DatabaseInfo {
}; };
} }
function testEqualNames(a: string, b: string, opts: DbDiffOptions) { function getNameWithoutDeletedPrefix(name: string, opts: DbDiffOptions, deletedPrefix?: string) {
if (deletedPrefix) {
if (opts.ignoreCase) {
if ((name || '').toLowerCase().startsWith(deletedPrefix.toLowerCase())) {
return name.slice(deletedPrefix.length);
}
} else {
if ((name || '').startsWith(deletedPrefix)) {
return name.slice(deletedPrefix.length);
}
}
}
return name;
}
function hasDeletedPrefix(name: string, opts: DbDiffOptions, deletedPrefix?: string) {
if (deletedPrefix) {
if (opts.ignoreCase) {
return (name || '').toLowerCase().startsWith(deletedPrefix.toLowerCase());
} else {
return (name || '').startsWith(deletedPrefix);
}
}
return false;
}
function testEqualNames(a: string, b: string, opts: DbDiffOptions, deletedPrefix?: string) {
a = getNameWithoutDeletedPrefix(a, opts, deletedPrefix);
b = getNameWithoutDeletedPrefix(b, opts, deletedPrefix);
if (opts.ignoreCase) return (a || '').toLowerCase() == (b || '').toLowerCase(); if (opts.ignoreCase) return (a || '').toLowerCase() == (b || '').toLowerCase();
return a == b; return a == b;
} }
@@ -157,9 +186,12 @@ function testEqualSchemas(lschema: string, rschema: string, opts: DbDiffOptions)
return testEqualNames(lschema, rschema, opts); return testEqualNames(lschema, rschema, opts);
} }
function testEqualFullNames(lft: NamedObjectInfo, rgt: NamedObjectInfo, opts: DbDiffOptions) { function testEqualFullNames(lft: NamedObjectInfo, rgt: NamedObjectInfo, opts: DbDiffOptions, deletedPrefix?: string) {
if (lft == null || rgt == null) return lft == rgt; if (lft == null || rgt == null) return lft == rgt;
return testEqualSchemas(lft.schemaName, rgt.schemaName, opts) && testEqualNames(lft.pureName, rgt.pureName, opts); return (
testEqualSchemas(lft.schemaName, rgt.schemaName, opts) &&
testEqualNames(lft.pureName, rgt.pureName, opts, deletedPrefix)
);
} }
function testEqualDefaultValues(value1: string | null | undefined, value2: string | null | undefined) { function testEqualDefaultValues(value1: string | null | undefined, value2: string | null | undefined) {
@@ -428,7 +460,11 @@ function planAlterTable(plan: AlterPlan, oldTable: TableInfo, newTable: TableInf
if (opts.deletedColumnPrefix) { if (opts.deletedColumnPrefix) {
columnPairs columnPairs
.filter(x => x[1] == null) .filter(x => x[1] == null)
.forEach(x => plan.renameColumn(x[0], opts.deletedColumnPrefix + x[0].columnName)); .forEach(x => {
if (!hasDeletedPrefix(x[0].columnName, opts, opts.deletedColumnPrefix)) {
plan.renameColumn(x[0], opts.deletedColumnPrefix + x[0].columnName);
}
});
} else if (!opts.noDropColumn) { } else 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]));
} }
@@ -437,6 +473,10 @@ function planAlterTable(plan: AlterPlan, oldTable: TableInfo, newTable: TableInf
plan.renameTable(oldTable, newTable.pureName); plan.renameTable(oldTable, newTable.pureName);
} }
if (hasDeletedPrefix(oldTable.pureName, opts, opts.deletedTablePrefix)) {
plan.renameTable(oldTable, newTable.pureName);
}
columnPairs.filter(x => x[0] == null).forEach(x => plan.createColumn(x[1])); columnPairs.filter(x => x[0] == null).forEach(x => plan.createColumn(x[1]));
columnPairs columnPairs
@@ -577,7 +617,7 @@ export function createAlterDatabasePlan(
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) { if (newobj == null) {
if (opts.deletedTablePrefix) { if (opts.deletedTablePrefix && !hasDeletedPrefix(oldobj.pureName, opts, opts.deletedTablePrefix)) {
plan.renameTable(oldobj, opts.deletedTablePrefix + oldobj.pureName); plan.renameTable(oldobj, opts.deletedTablePrefix + oldobj.pureName);
} else if (!opts.noDropTable) { } else if (!opts.noDropTable) {
plan.dropTable(oldobj); plan.dropTable(oldobj);
@@ -587,7 +627,11 @@ export function createAlterDatabasePlan(
} }
} else { } else {
if (newobj == null) { if (newobj == null) {
if (opts.deletedSqlObjectPrefix && driver.dialect.renameSqlObject) { if (
opts.deletedSqlObjectPrefix &&
driver.dialect.renameSqlObject &&
!hasDeletedPrefix(oldobj.pureName, opts, opts.deletedSqlObjectPrefix)
) {
plan.renameSqlObject(oldobj, opts.deletedSqlObjectPrefix + oldobj.pureName); plan.renameSqlObject(oldobj, opts.deletedSqlObjectPrefix + oldobj.pureName);
} else if (!opts.noDropSqlObject) { } else if (!opts.noDropSqlObject) {
plan.dropSqlObject(oldobj); plan.dropSqlObject(oldobj);
@@ -669,7 +713,14 @@ export function matchPairedObjects(db1: DatabaseInfo, db2: DatabaseInfo, opts: D
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 => testEqualFullNames(x, obj2, opts)); const obj1 = db1[objectTypeField].find(x =>
testEqualFullNames(
x,
obj2,
opts,
objectTypeField == 'tables' ? opts.deletedTablePrefix : opts.deletedSqlObjectPrefix
)
);
if (obj1) { if (obj1) {
obj2.pairingId = obj1.pairingId; obj2.pairingId = obj1.pairingId;