diff --git a/packages/datalib/src/ChangeSet.ts b/packages/datalib/src/ChangeSet.ts index 5fd2cfe7b..a9a7011b8 100644 --- a/packages/datalib/src/ChangeSet.ts +++ b/packages/datalib/src/ChangeSet.ts @@ -300,7 +300,7 @@ function changeSetUpdateToSql(item: ChangeSetItem): Update { }; } -export function changeSetDeleteToSql(item: ChangeSetItem): Delete { +function changeSetDeleteToSql(item: ChangeSetItem): Delete { return { from: { name: { diff --git a/packages/datalib/src/deleteCascade.ts b/packages/datalib/src/deleteCascade.ts index d4c09fcb8..781582753 100644 --- a/packages/datalib/src/deleteCascade.ts +++ b/packages/datalib/src/deleteCascade.ts @@ -1,117 +1,136 @@ import _ from 'lodash'; import { Command, Insert, Update, Delete, UpdateField, Condition, AllowIdentityInsert } from 'dbgate-sqltree'; -import { NamedObjectInfo, DatabaseInfo } from 'dbgate-types'; -import { ChangeSet, extractChangeSetCondition, changeSetDeleteToSql } from './ChangeSet'; +import { NamedObjectInfo, DatabaseInfo, ForeignKeyInfo, TableInfo } from 'dbgate-types'; +import { ChangeSet, ChangeSetItem, extractChangeSetCondition } from './ChangeSet'; export interface ChangeSetDeleteCascade { title: string; commands: Command[]; } +// function getDeleteScript() + +function processDependencies( + changeSet: ChangeSet, + result: ChangeSetDeleteCascade[], + allForeignKeys: ForeignKeyInfo[], + fkPath: ForeignKeyInfo[], + table: TableInfo, + baseCmd: ChangeSetItem, + dbinfo: DatabaseInfo +) { + const dependencies = allForeignKeys.filter( + x => x.refSchemaName == table.schemaName && x.refTableName == table.pureName + ); + + 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 = { + commandType: 'delete', + from: { + name: { + pureName: fk.pureName, + schemaName: fk.schemaName, + }, + }, + where: { + conditionType: 'exists', + subQuery: { + commandType: 'select', + from: { + name: { + pureName: fk.pureName, + schemaName: fk.schemaName, + }, + alias: 't0', + relations: subFkPath.map((fkItem, fkIndex) => ({ + joinType: 'INNER JOIN', + alias: `t${fkIndex + 1}`, + name: { + pureName: fkItem.refTableName, + schemaName: fkItem.refSchemaName, + }, + conditions: fkItem.columns.map(column => ({ + conditionType: 'binary', + operator: '=', + left: { + exprType: 'column', + columnName: column.columnName, + source: { alias: `t${fkIndex}` }, + }, + right: { + exprType: 'column', + columnName: column.refColumnName, + source: { alias: `t${fkIndex + 1}` }, + }, + })), + })), + }, + where: { + conditionType: 'and', + conditions: [ + extractChangeSetCondition(baseCmd, `t${subFkPath.length}`), + // @ts-ignore + ...table.primaryKey.columns.map(column => ({ + conditionType: 'binary', + operator: '=', + left: { + exprType: 'column', + columnName: column.columnName, + source: { alias: 't0' }, + }, + right: { + exprType: 'column', + columnName: column.columnName, + source: { + name: fk, + }, + }, + })), + ], + }, + }, + }, + }; + let resItem = result.find(x => x.title == fk.pureName); + if (!resItem) { + resItem = { + title: fk.pureName, + commands: [], + }; + result.push(resItem); + } + resItem.commands.push(refCmd); + } +} + export function getDeleteCascades(changeSet: ChangeSet, dbinfo: DatabaseInfo): ChangeSetDeleteCascade[] { - const res: 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; - const dependencies = allForeignKeys.filter( - x => x.refSchemaName == table.schemaName && x.refTableName == table.pureName - ); - for (const fk of dependencies) { - const refCmd: Delete = { - commandType: 'delete', - from: { - name: { - pureName: fk.pureName, - schemaName: fk.schemaName, - }, - }, - where: { - conditionType: 'exists', - subQuery: { - commandType: 'select', - from: { - name: { - pureName: fk.pureName, - schemaName: fk.schemaName, - }, - alias: 't1', - relations: [ - { - joinType: 'INNER JOIN', - alias: 't2', - name: { - pureName: fk.refTableName, - schemaName: fk.refSchemaName, - }, - conditions: fk.columns.map(column => ({ - conditionType: 'binary', - operator: '=', - left: { - exprType: 'column', - columnName: column.columnName, - source: { alias: 't1' }, - }, - right: { - exprType: 'column', - columnName: column.refColumnName, - source: { alias: 't2' }, - }, - })), - }, - ], - }, - where: { - conditionType: 'and', - conditions: [ - extractChangeSetCondition(baseCmd, 't2'), - // @ts-ignore - ...table.primaryKey.columns.map(column => ({ - conditionType: 'binary', - operator: '=', - left: { - exprType: 'column', - columnName: column.columnName, - source: { alias: 't1' }, - }, - right: { - exprType: 'column', - columnName: column.columnName, - source: { - name: { - pureName: fk.refTableName, - schemaName: fk.refSchemaName, - }, - }, - }, - })), - ], - }, - }, - }, - }; - let resItem = res.find(x => x.title == fk.pureName); - if (!resItem) { - resItem = { - title: fk.pureName, - commands: [], - }; - res.push(resItem); - } - resItem.commands.push(refCmd); - } - let resItem = res.find(x => x.title == baseCmd.pureName); - if (!resItem) { - resItem = { - title: baseCmd.pureName, - commands: [], - }; - res.push(resItem); - } + processDependencies(changeSet, result, allForeignKeys, [], table, baseCmd, dbinfo); - resItem.commands.push(changeSetDeleteToSql(baseCmd)); + // 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; } diff --git a/packages/web/src/modals/ConfirmSqlModal.svelte b/packages/web/src/modals/ConfirmSqlModal.svelte index a7bef44dd..76481f6e8 100644 --- a/packages/web/src/modals/ConfirmSqlModal.svelte +++ b/packages/web/src/modals/ConfirmSqlModal.svelte @@ -32,10 +32,12 @@ values[`deleteReferences_${title}`] !== false) - .map(({ script, title }) => script) - .join('\n') + ? [ + ...deleteCascadesScripts + .filter(({ script, title }) => values[`deleteReferences_${title}`] !== false) + .map(({ script, title }) => script), + sql, + ].join('\n') : sql} readOnly /> @@ -43,23 +45,32 @@ {#if !_.isEmpty(deleteCascadesScripts)} - +
+ +
{/if} {#if values.deleteReferencesCascade} - {#each _.sortBy(deleteCascadesScripts, 'title') as deleteTable} - - {/each} + + +
+ {#each _.sortBy(deleteCascadesScripts, 'title') as deleteTable} +
+ +
+ {/each} +
{/if}
@@ -86,10 +97,12 @@ closeCurrentModal(); onConfirm( e.detail.deleteReferencesCascade - ? deleteCascadesScripts - .filter(({ script, title }) => e.detail[`deleteReferences_${title}`] !== false) - .map(({ script, title }) => script) - .join('\n') + ? [ + ...deleteCascadesScripts + .filter(({ script, title }) => e.detail[`deleteReferences_${title}`] !== false) + .map(({ script, title }) => script), + sql, + ].join('\n') : null ); }}