delete cascade WIP

This commit is contained in:
Jan Prochazka
2021-09-28 11:32:23 +02:00
parent 8910a6bd07
commit 2c2b4fc08f
3 changed files with 153 additions and 6 deletions

View File

@@ -11,6 +11,11 @@ export interface ChangeSetItem {
fields?: { [column: string]: string }; fields?: { [column: string]: string };
} }
export interface ChangeSetDeleteCascade {
title: string;
commands: Command[];
}
export interface ChangeSet { export interface ChangeSet {
inserts: ChangeSetItem[]; inserts: ChangeSetItem[];
updates: ChangeSetItem[]; updates: ChangeSetItem[];
@@ -118,7 +123,11 @@ export function setChangeSetValue(
}; };
} }
export function setChangeSetRowData(changeSet: ChangeSet, definition: ChangeSetRowDefinition, document: any): ChangeSet { export function setChangeSetRowData(
changeSet: ChangeSet,
definition: ChangeSetRowDefinition,
document: any
): ChangeSet {
if (!changeSet || !definition) return changeSet; if (!changeSet || !definition) return changeSet;
let [fieldName, existingItem] = findExistingChangeSetItem(changeSet, definition); let [fieldName, existingItem] = findExistingChangeSetItem(changeSet, definition);
if (fieldName == 'deletes') { if (fieldName == 'deletes') {
@@ -257,7 +266,7 @@ function insertToSql(
]; ];
} }
function extractCondition(item: ChangeSetItem): Condition { function extractCondition(item: ChangeSetItem, alias?: string): Condition {
return { return {
conditionType: 'and', conditionType: 'and',
conditions: _.keys(item.condition).map(columnName => ({ conditions: _.keys(item.condition).map(columnName => ({
@@ -271,6 +280,7 @@ function extractCondition(item: ChangeSetItem): Condition {
pureName: item.pureName, pureName: item.pureName,
schemaName: item.schemaName, schemaName: item.schemaName,
}, },
alias,
}, },
}, },
right: { right: {
@@ -318,6 +328,102 @@ export function changeSetToSql(changeSet: ChangeSet, dbinfo: DatabaseInfo): Comm
); );
} }
export function getDeleteCascades(changeSet: ChangeSet, dbinfo: DatabaseInfo): ChangeSetDeleteCascade[] {
const res: 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);
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: [
extractCondition(baseCmd, 't2'),
// @ts-ignore
...fk.columns.map(column => ({
conditionType: 'binary',
operator: '=',
left: {
exprType: 'column',
columnName: column.columnName,
source: { alias: 't1' },
},
right: {
exprType: 'column',
columnName: column.refColumnName,
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);
}
}
return res;
}
export function revertChangeSetRowChanges(changeSet: ChangeSet, definition: ChangeSetRowDefinition): ChangeSet { export function revertChangeSetRowChanges(changeSet: ChangeSet, definition: ChangeSetRowDefinition): ChangeSet {
// console.log('definition', definition); // console.log('definition', definition);
const [field, item] = findExistingChangeSetItem(changeSet, definition); const [field, item] = findExistingChangeSetItem(changeSet, definition);

View File

@@ -1,5 +1,6 @@
<script> <script>
import _ from 'lodash'; import { keys } from 'localforage';
import _, { isEmpty } from 'lodash';
import FormStyledButton from '../elements/FormStyledButton.svelte'; import FormStyledButton from '../elements/FormStyledButton.svelte';
import FormCheckboxField from '../forms/FormCheckboxField.svelte'; import FormCheckboxField from '../forms/FormCheckboxField.svelte';
import FormProvider from '../forms/FormProvider.svelte'; import FormProvider from '../forms/FormProvider.svelte';
@@ -15,6 +16,7 @@
export let onConfirm; export let onConfirm;
export let engine; export let engine;
export let recreates; export let recreates;
export let deleteCascadesScripts;
$: isRecreated = _.sum(_.values(recreates || {})) > 0; $: isRecreated = _.sum(_.values(recreates || {})) > 0;
@@ -26,9 +28,41 @@
<div slot="header">Save changes</div> <div slot="header">Save changes</div>
<div class="editor"> <div class="editor">
<SqlEditor {engine} value={sql} readOnly /> <FormValues let:values>
<SqlEditor
{engine}
value={values.deleteReferencesCascade
? deleteCascadesScripts
.filter(({ script, title }) => values[`deleteReferences_${title}`] !== false)
.map(({ script, title }) => script)
.join('\n')
: sql}
readOnly
/>
</FormValues>
</div> </div>
{#if !_.isEmpty(deleteCascadesScripts)}
<FormCheckboxField
templateProps={{ noMargin: true }}
label="Delete references CASCADE"
name="deleteReferencesCascade"
/>
{/if}
<FormValues let:values>
{#if values.deleteReferencesCascade}
{#each _.sortBy(deleteCascadesScripts, 'title') as deleteTable}
<FormCheckboxField
defaultValue={true}
templateProps={{ noMargin: true }}
label={deleteTable.title}
name={`deleteReferences_${deleteTable.title}`}
/>
{/each}
{/if}
</FormValues>
{#if isRecreated} {#if isRecreated}
<div class="form-margin"> <div class="form-margin">
<div> <div>

View File

@@ -16,10 +16,10 @@
export const matchingProps = ['conid', 'database', 'schemaName', 'pureName']; export const matchingProps = ['conid', 'database', 'schemaName', 'pureName'];
export const allowAddToFavorites = props => true; export const allowAddToFavorites = props => true;
</script> </script>
<script lang="ts"> <script lang="ts">
import _ from 'lodash';
import App from '../App.svelte'; import App from '../App.svelte';
import TableDataGrid from '../datagrid/TableDataGrid.svelte'; import TableDataGrid from '../datagrid/TableDataGrid.svelte';
import useGridConfig from '../utility/useGridConfig'; import useGridConfig from '../utility/useGridConfig';
@@ -29,6 +29,7 @@
createChangeSet, createChangeSet,
createGridCache, createGridCache,
createGridConfig, createGridConfig,
getDeleteCascades,
TableFormViewDisplay, TableFormViewDisplay,
TableGridDisplay, TableGridDisplay,
} from 'dbgate-datalib'; } from 'dbgate-datalib';
@@ -87,11 +88,18 @@
export function save() { export function save() {
const driver = findEngineDriver($connection, $extensions); const driver = findEngineDriver($connection, $extensions);
const script = changeSetToSql($changeSetStore?.value, $dbinfo); const script = changeSetToSql($changeSetStore?.value, $dbinfo);
const deleteCascades = getDeleteCascades($changeSetStore?.value, $dbinfo);
const sql = scriptToSql(driver, script); const sql = scriptToSql(driver, script);
const deleteCascadesScripts = _.map(deleteCascades, ({ title, commands }) => ({
title,
script: scriptToSql(driver, commands),
}));
console.log('deleteCascadesScripts', deleteCascadesScripts);
showModal(ConfirmSqlModal, { showModal(ConfirmSqlModal, {
sql, sql,
onConfirm: () => handleConfirmSql(sql), onConfirm: () => handleConfirmSql(sql),
engine: driver.engine, engine: driver.engine,
deleteCascadesScripts,
}); });
} }
@@ -105,7 +113,6 @@
} }
registerMenu({ command: 'tableData.save', tag: 'save' }); registerMenu({ command: 'tableData.save', tag: 'save' });
</script> </script>
<TableDataGrid <TableDataGrid