recursive delete cascade

This commit is contained in:
Jan Prochazka
2021-09-28 19:03:53 +02:00
parent 12c9e638f5
commit 571be4b3f9
3 changed files with 153 additions and 121 deletions

View File

@@ -300,7 +300,7 @@ function changeSetUpdateToSql(item: ChangeSetItem): Update {
}; };
} }
export function changeSetDeleteToSql(item: ChangeSetItem): Delete { function changeSetDeleteToSql(item: ChangeSetItem): Delete {
return { return {
from: { from: {
name: { name: {

View File

@@ -1,23 +1,38 @@
import _ from 'lodash'; import _ from 'lodash';
import { Command, Insert, Update, Delete, UpdateField, Condition, AllowIdentityInsert } from 'dbgate-sqltree'; import { Command, Insert, Update, Delete, UpdateField, Condition, AllowIdentityInsert } from 'dbgate-sqltree';
import { NamedObjectInfo, DatabaseInfo } from 'dbgate-types'; import { NamedObjectInfo, DatabaseInfo, ForeignKeyInfo, TableInfo } from 'dbgate-types';
import { ChangeSet, extractChangeSetCondition, changeSetDeleteToSql } from './ChangeSet'; import { ChangeSet, ChangeSetItem, extractChangeSetCondition } from './ChangeSet';
export interface ChangeSetDeleteCascade { export interface ChangeSetDeleteCascade {
title: string; title: string;
commands: Command[]; commands: Command[];
} }
export function getDeleteCascades(changeSet: ChangeSet, dbinfo: DatabaseInfo): ChangeSetDeleteCascade[] { // function getDeleteScript()
const res: ChangeSetDeleteCascade[] = [];
const allForeignKeys = _.flatten(dbinfo.tables.map(x => x.foreignKeys)); function processDependencies(
for (const baseCmd of changeSet.deletes) { changeSet: ChangeSet,
const table = dbinfo.tables.find(x => x.pureName == baseCmd.pureName && x.schemaName == baseCmd.schemaName); result: ChangeSetDeleteCascade[],
if (!table.primaryKey) continue; allForeignKeys: ForeignKeyInfo[],
fkPath: ForeignKeyInfo[],
table: TableInfo,
baseCmd: ChangeSetItem,
dbinfo: DatabaseInfo
) {
const dependencies = allForeignKeys.filter( const dependencies = allForeignKeys.filter(
x => x.refSchemaName == table.schemaName && x.refTableName == table.pureName x => x.refSchemaName == table.schemaName && x.refTableName == table.pureName
); );
for (const fk of dependencies) { for (const fk of dependencies) {
if (fk.pureName == baseCmd.pureName) continue;
if (result.find(x => x.title == fk.pureName)) continue;
const depTable = dbinfo.tables.find(x => x.pureName == fk.pureName && x.schemaName == fk.schemaName);
const subFkPath = [...fkPath, fk];
if (depTable) {
processDependencies(changeSet, result, allForeignKeys, subFkPath, depTable, baseCmd, dbinfo);
}
const refCmd: Delete = { const refCmd: Delete = {
commandType: 'delete', commandType: 'delete',
from: { from: {
@@ -35,36 +50,34 @@ export function getDeleteCascades(changeSet: ChangeSet, dbinfo: DatabaseInfo): C
pureName: fk.pureName, pureName: fk.pureName,
schemaName: fk.schemaName, schemaName: fk.schemaName,
}, },
alias: 't1', alias: 't0',
relations: [ relations: subFkPath.map((fkItem, fkIndex) => ({
{
joinType: 'INNER JOIN', joinType: 'INNER JOIN',
alias: 't2', alias: `t${fkIndex + 1}`,
name: { name: {
pureName: fk.refTableName, pureName: fkItem.refTableName,
schemaName: fk.refSchemaName, schemaName: fkItem.refSchemaName,
}, },
conditions: fk.columns.map(column => ({ conditions: fkItem.columns.map(column => ({
conditionType: 'binary', conditionType: 'binary',
operator: '=', operator: '=',
left: { left: {
exprType: 'column', exprType: 'column',
columnName: column.columnName, columnName: column.columnName,
source: { alias: 't1' }, source: { alias: `t${fkIndex}` },
}, },
right: { right: {
exprType: 'column', exprType: 'column',
columnName: column.refColumnName, columnName: column.refColumnName,
source: { alias: 't2' }, source: { alias: `t${fkIndex + 1}` },
}, },
})), })),
}, })),
],
}, },
where: { where: {
conditionType: 'and', conditionType: 'and',
conditions: [ conditions: [
extractChangeSetCondition(baseCmd, 't2'), extractChangeSetCondition(baseCmd, `t${subFkPath.length}`),
// @ts-ignore // @ts-ignore
...table.primaryKey.columns.map(column => ({ ...table.primaryKey.columns.map(column => ({
conditionType: 'binary', conditionType: 'binary',
@@ -72,16 +85,13 @@ export function getDeleteCascades(changeSet: ChangeSet, dbinfo: DatabaseInfo): C
left: { left: {
exprType: 'column', exprType: 'column',
columnName: column.columnName, columnName: column.columnName,
source: { alias: 't1' }, source: { alias: 't0' },
}, },
right: { right: {
exprType: 'column', exprType: 'column',
columnName: column.columnName, columnName: column.columnName,
source: { source: {
name: { name: fk,
pureName: fk.refTableName,
schemaName: fk.refSchemaName,
},
}, },
}, },
})), })),
@@ -90,28 +100,37 @@ export function getDeleteCascades(changeSet: ChangeSet, dbinfo: DatabaseInfo): C
}, },
}, },
}; };
let resItem = res.find(x => x.title == fk.pureName); let resItem = result.find(x => x.title == fk.pureName);
if (!resItem) { if (!resItem) {
resItem = { resItem = {
title: fk.pureName, title: fk.pureName,
commands: [], commands: [],
}; };
res.push(resItem); result.push(resItem);
} }
resItem.commands.push(refCmd); resItem.commands.push(refCmd);
} }
let resItem = res.find(x => x.title == baseCmd.pureName);
if (!resItem) {
resItem = {
title: baseCmd.pureName,
commands: [],
};
res.push(resItem);
} }
resItem.commands.push(changeSetDeleteToSql(baseCmd)); export function getDeleteCascades(changeSet: ChangeSet, dbinfo: DatabaseInfo): ChangeSetDeleteCascade[] {
const result: ChangeSetDeleteCascade[] = [];
const allForeignKeys = _.flatten(dbinfo.tables.map(x => x.foreignKeys));
for (const baseCmd of changeSet.deletes) {
const table = dbinfo.tables.find(x => x.pureName == baseCmd.pureName && x.schemaName == baseCmd.schemaName);
if (!table.primaryKey) continue;
processDependencies(changeSet, result, allForeignKeys, [], table, baseCmd, dbinfo);
// let resItem = result.find(x => x.title == baseCmd.pureName);
// if (!resItem) {
// resItem = {
// title: baseCmd.pureName,
// commands: [],
// };
// result.push(resItem);
// }
// resItem.commands.push(changeSetDeleteToSql(baseCmd));
} }
return res; return result;
} }

View File

@@ -32,10 +32,12 @@
<SqlEditor <SqlEditor
{engine} {engine}
value={values.deleteReferencesCascade value={values.deleteReferencesCascade
? deleteCascadesScripts ? [
...deleteCascadesScripts
.filter(({ script, title }) => values[`deleteReferences_${title}`] !== false) .filter(({ script, title }) => values[`deleteReferences_${title}`] !== false)
.map(({ script, title }) => script) .map(({ script, title }) => script),
.join('\n') sql,
].join('\n')
: sql} : sql}
readOnly readOnly
/> />
@@ -43,23 +45,32 @@
</div> </div>
{#if !_.isEmpty(deleteCascadesScripts)} {#if !_.isEmpty(deleteCascadesScripts)}
<div class="mt-2">
<FormCheckboxField <FormCheckboxField
templateProps={{ noMargin: true }} templateProps={{ noMargin: true }}
label="Delete references CASCADE" label="Delete references CASCADE"
name="deleteReferencesCascade" name="deleteReferencesCascade"
/> />
</div>
{/if} {/if}
<FormValues let:values> <FormValues let:values>
{#if values.deleteReferencesCascade} {#if values.deleteReferencesCascade}
<!-- <div class="form-margin flex">
</div> -->
<div class="form-margin flex">
{#each _.sortBy(deleteCascadesScripts, 'title') as deleteTable} {#each _.sortBy(deleteCascadesScripts, 'title') as deleteTable}
<div class="mr-1">
<FormCheckboxField <FormCheckboxField
defaultValue={true} defaultValue={true}
templateProps={{ noMargin: true }} templateProps={{ noMargin: true }}
label={deleteTable.title} label={deleteTable.title}
name={`deleteReferences_${deleteTable.title}`} name={`deleteReferences_${deleteTable.title}`}
/> />
</div>
{/each} {/each}
</div>
{/if} {/if}
</FormValues> </FormValues>
@@ -86,10 +97,12 @@
closeCurrentModal(); closeCurrentModal();
onConfirm( onConfirm(
e.detail.deleteReferencesCascade e.detail.deleteReferencesCascade
? deleteCascadesScripts ? [
...deleteCascadesScripts
.filter(({ script, title }) => e.detail[`deleteReferences_${title}`] !== false) .filter(({ script, title }) => e.detail[`deleteReferences_${title}`] !== false)
.map(({ script, title }) => script) .map(({ script, title }) => script),
.join('\n') sql,
].join('\n')
: null : null
); );
}} }}