Skip to content

Commit 8f02734

Browse files
committed
Support all deletion actions instead of only cascade delete
1 parent 4855bfe commit 8f02734

File tree

8 files changed

+121
-35
lines changed

8 files changed

+121
-35
lines changed

studio/components/interfaces/TableGridEditor/SidePanelEditor/ColumnEditor/ColumnEditor.utils.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -244,7 +244,7 @@ export const getForeignKeyDeletionAction = (deletionAction?: string) => {
244244
case FOREIGN_KEY_DELETION_ACTION.SET_DEFAULT:
245245
return 'Set default'
246246
case FOREIGN_KEY_DELETION_ACTION.SET_NULL:
247-
return 'Set null'
247+
return 'Set NULL'
248248
default:
249249
return undefined
250250
}

studio/components/interfaces/TableGridEditor/SidePanelEditor/ColumnEditor/ColumnForeignKey.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -256,7 +256,9 @@ const ColumnForeignKeyUpdated: FC<{
256256
</Badge>
257257
)}
258258
{updatedDeletionAction !== undefined && (
259-
<Badge color="green">On delete: {updatedDeletionAction}</Badge>
259+
<div>
260+
<Badge color="green">On delete: {updatedDeletionAction}</Badge>
261+
</div>
260262
)}
261263
</div>
262264
</div>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { FOREIGN_KEY_DELETION_ACTION } from 'data/database/database-query-constants'
2+
3+
export const FOREIGN_KEY_DELETION_OPTIONS = [
4+
{ key: 'no-action', label: 'No action', value: FOREIGN_KEY_DELETION_ACTION.NO_ACTION },
5+
{ key: 'cascade', label: 'Cascade', value: FOREIGN_KEY_DELETION_ACTION.CASCADE },
6+
{ key: 'restrict', label: 'Restrict', value: FOREIGN_KEY_DELETION_ACTION.RESTRICT },
7+
{ key: 'set-default', label: 'Set default', value: FOREIGN_KEY_DELETION_ACTION.SET_DEFAULT },
8+
{ key: 'set-null', label: 'Set NULL', value: FOREIGN_KEY_DELETION_ACTION.SET_NULL },
9+
]

studio/components/interfaces/TableGridEditor/SidePanelEditor/ForeignKeySelector/ForeignKeySelector.tsx

Lines changed: 44 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import { ForeignKey } from './ForeignKeySelector.types'
1010
import { ColumnField } from '../SidePanelEditor.types'
1111
import InformationBox from 'components/ui/InformationBox'
1212
import { FOREIGN_KEY_DELETION_ACTION } from 'data/database/database-query-constants'
13+
import { FOREIGN_KEY_DELETION_OPTIONS } from './ForeignKeySelector.constants'
14+
import { generateDeletionActionDescription } from './ForeignKeySelector.utils'
1315

1416
interface Props {
1517
column: ColumnField
@@ -21,17 +23,14 @@ interface Props {
2123
) => void
2224
}
2325

24-
// [Joshen] In the future, we can easily extend to other deletion actions as well by replacing
25-
// enableCascadeDelete with deletionAction
26-
2726
const ForeignKeySelector: FC<Props> = ({ column, visible = false, closePanel, saveChanges }) => {
2827
const { meta } = useStore()
2928
const [errors, setErrors] = useState<any>({})
3029
const [selectedForeignKey, setSelectedForeignKey] = useState<ForeignKey>({
3130
schema: 'public',
3231
table: '',
3332
column: '',
34-
enableCascadeDelete: false,
33+
deletionAction: FOREIGN_KEY_DELETION_ACTION.NO_ACTION,
3534
})
3635

3736
const schemas = meta.schemas.list()
@@ -58,22 +57,27 @@ const ForeignKeySelector: FC<Props> = ({ column, visible = false, closePanel, sa
5857
schema: foreignKey.target_table_schema,
5958
table: foreignKey.target_table_name,
6059
column: foreignKey.target_column_name,
61-
enableCascadeDelete: foreignKey.deletion_action === FOREIGN_KEY_DELETION_ACTION.CASCADE,
60+
deletionAction: foreignKey.deletion_action,
6261
})
6362
} else {
6463
setSelectedForeignKey({
6564
schema: 'public',
6665
table: '',
6766
column: '',
68-
enableCascadeDelete: false,
67+
deletionAction: FOREIGN_KEY_DELETION_ACTION.NO_ACTION,
6968
})
7069
}
7170
}
7271
}, [visible])
7372

7473
const updateSelectedSchema = (schema: string) => {
7574
meta.tables.loadBySchema(schema)
76-
const updatedForeignKey = { schema, table: '', column: '', enableCascadeDelete: false }
75+
const updatedForeignKey = {
76+
schema,
77+
table: '',
78+
column: '',
79+
deletionAction: FOREIGN_KEY_DELETION_ACTION.NO_ACTION,
80+
}
7781
setSelectedForeignKey(updatedForeignKey)
7882
}
7983

@@ -84,7 +88,7 @@ const ForeignKeySelector: FC<Props> = ({ column, visible = false, closePanel, sa
8488
schema: '',
8589
table: '',
8690
column: '',
87-
enableCascadeDelete: false,
91+
deletionAction: FOREIGN_KEY_DELETION_ACTION.NO_ACTION,
8892
})
8993
}
9094
const table = find(tables, { id: tableId })
@@ -93,7 +97,7 @@ const ForeignKeySelector: FC<Props> = ({ column, visible = false, closePanel, sa
9397
schema: table.schema,
9498
table: table.name,
9599
column: table.columns?.length ? table.columns[0].name : undefined,
96-
enableCascadeDelete: false,
100+
deletionAction: FOREIGN_KEY_DELETION_ACTION.NO_ACTION,
97101
})
98102
}
99103
}
@@ -107,9 +111,9 @@ const ForeignKeySelector: FC<Props> = ({ column, visible = false, closePanel, sa
107111
}
108112
}
109113

110-
const updateEnableCascadeDelete = (value: boolean) => {
114+
const updateDeletionAction = (value: string) => {
111115
setErrors({})
112-
setSelectedForeignKey({ ...selectedForeignKey, enableCascadeDelete: value })
116+
setSelectedForeignKey({ ...selectedForeignKey, deletionAction: value })
113117
}
114118

115119
const onSaveChanges = (resolve: () => void) => {
@@ -129,9 +133,7 @@ const ForeignKeySelector: FC<Props> = ({ column, visible = false, closePanel, sa
129133
saveChanges({
130134
table: selectedTable,
131135
column: selectedColumn,
132-
deletionAction: selectedForeignKey.enableCascadeDelete
133-
? FOREIGN_KEY_DELETION_ACTION.CASCADE
134-
: FOREIGN_KEY_DELETION_ACTION.NO_ACTION,
136+
deletionAction: selectedForeignKey.deletionAction,
135137
})
136138
}
137139
}
@@ -233,7 +235,11 @@ const ForeignKeySelector: FC<Props> = ({ column, visible = false, closePanel, sa
233235
disabled
234236
label={
235237
<div>
236-
Select a column from <code>{selectedForeignKey?.table}</code> to reference to
238+
Select a column from{' '}
239+
<code className="text-xs">
240+
{selectedForeignKey?.schema}.{selectedForeignKey?.table}
241+
</code>{' '}
242+
to reference to
237243
</div>
238244
}
239245
error={errors.column}
@@ -246,7 +252,11 @@ const ForeignKeySelector: FC<Props> = ({ column, visible = false, closePanel, sa
246252
// @ts-ignore
247253
label={
248254
<div>
249-
Select a column from <code>{selectedForeignKey?.table}</code> to reference to
255+
Select a column from{' '}
256+
<code className="text-xs">
257+
{selectedForeignKey?.schema}.{selectedForeignKey?.table}
258+
</code>{' '}
259+
to reference to
250260
</div>
251261
}
252262
error={errors.column}
@@ -262,13 +272,25 @@ const ForeignKeySelector: FC<Props> = ({ column, visible = false, closePanel, sa
262272
))}
263273
</Listbox>
264274
)}
265-
<Toggle
266-
label="Enable cascade deletes"
275+
<SidePanel.Separator />
276+
<Listbox
277+
id="deletionAction"
278+
value={selectedForeignKey.deletionAction}
279+
label="Deletion action if referenced row is removed"
267280
// @ts-ignore
268-
onChange={(value: boolean) => updateEnableCascadeDelete(value)}
269-
checked={selectedForeignKey.enableCascadeDelete}
270-
descriptionText={`Deleting a record from ${selectedForeignKey.schema}.${selectedForeignKey.table} will also delete the referencing records from this table`}
271-
/>
281+
descriptionText={generateDeletionActionDescription(
282+
selectedForeignKey.deletionAction,
283+
`${selectedForeignKey.schema}.${selectedForeignKey.table}`
284+
)}
285+
error={errors.column}
286+
onChange={(value: string) => updateDeletionAction(value)}
287+
>
288+
{FOREIGN_KEY_DELETION_OPTIONS.map((option) => (
289+
<Listbox.Option key={option.key} value={option.value} label={option.label}>
290+
<p className="text-scale-1200">{option.label}</p>
291+
</Listbox.Option>
292+
))}
293+
</Listbox>
272294
</>
273295
)}
274296
</div>

studio/components/interfaces/TableGridEditor/SidePanelEditor/ForeignKeySelector/ForeignKeySelector.types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,5 @@ export interface ForeignKey {
22
schema: string
33
table: string
44
column?: string
5-
enableCascadeDelete: boolean
5+
deletionAction: string
66
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import { FOREIGN_KEY_DELETION_ACTION } from 'data/database/database-query-constants'
2+
3+
export const generateDeletionActionDescription = (deletionAction: string, reference: string) => {
4+
switch (deletionAction) {
5+
case FOREIGN_KEY_DELETION_ACTION.NO_ACTION:
6+
return (
7+
<>
8+
Deleting a record from <code className="text-xs text-scale-1100">{reference}</code> will{' '}
9+
<span className="text-brand-900 opacity-75">raise an error</span> if there exists
10+
referencing rows from this table
11+
</>
12+
)
13+
case FOREIGN_KEY_DELETION_ACTION.CASCADE:
14+
return (
15+
<>
16+
Deleting a record from <code className="text-xs text-scale-1100">{reference}</code> will{' '}
17+
<span className="text-brand-900 opacity-75">also delete</span> the referencing records
18+
from this table
19+
</>
20+
)
21+
case FOREIGN_KEY_DELETION_ACTION.RESTRICT:
22+
return (
23+
<>
24+
Deleting a record from <code className="text-xs text-scale-1100">{reference}</code> will{' '}
25+
<span className="text-brand-900 opacity-75">raise an error</span> if there exists
26+
referencing rows from this table
27+
</>
28+
)
29+
case FOREIGN_KEY_DELETION_ACTION.SET_DEFAULT:
30+
return (
31+
<>
32+
Deleting a record from <code className="text-xs text-scale-1100">{reference}</code> will{' '}
33+
set the values of existing referencing rows to their{' '}
34+
<span className="text-brand-900 opacity-75">default value</span> from this table
35+
</>
36+
)
37+
case FOREIGN_KEY_DELETION_ACTION.SET_NULL:
38+
return (
39+
<>
40+
Deleting a record from <code className="text-xs text-scale-1100">{reference}</code> will{' '}
41+
set the values of existing referencing rows{' '}
42+
<span className="text-brand-900 opacity-75">to NULL</span> from this table
43+
</>
44+
)
45+
}
46+
}

studio/data/database/database-query-constants.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ export enum FOREIGN_KEY_DELETION_ACTION {
44
NO_ACTION = 'a',
55
RESTRICT = 'r',
66
CASCADE = 'c',
7-
SET_NULL = 's',
7+
SET_NULL = 'n',
88
SET_DEFAULT = 'd',
99
}
1010

studio/stores/pgmeta/MetaStore.ts

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,8 @@ export interface IMetaStore {
102102
payload: UpdateColumnPayload,
103103
selectedTable: PostgresTable,
104104
foreignKey?: ExtendedPostgresRelationship,
105-
skipPKCreation?: boolean
105+
skipPKCreation?: boolean,
106+
skipSuccessMessage?: boolean
106107
) => any
107108
duplicateTable: (
108109
payload: any,
@@ -460,7 +461,8 @@ export default class MetaStore implements IMetaStore {
460461
payload: UpdateColumnPayload,
461462
selectedTable: PostgresTable,
462463
foreignKey?: ExtendedPostgresRelationship,
463-
skipPKCreation?: boolean
464+
skipPKCreation?: boolean,
465+
skipSuccessMessage: boolean = false
464466
) {
465467
try {
466468
const { isPrimaryKey, ...formattedPayload } = payload
@@ -489,19 +491,22 @@ export default class MetaStore implements IMetaStore {
489491

490492
// For updating of foreign key relationship, we remove the original one by default
491493
// Then just add whatever was in foreignKey - simplicity over trying to derive whether to update or not
492-
if (!isUndefined(existingForeignKey)) {
494+
if (existingForeignKey !== undefined) {
493495
const relation: any = await this.removeForeignKey(existingForeignKey)
494496
if (relation.error) throw relation.error
495497
}
496498

497-
if (!isUndefined(foreignKey)) {
499+
if (foreignKey !== undefined) {
498500
const relation: any = await this.addForeignKey(foreignKey)
499501
if (relation.error) throw relation.error
500502
}
501-
this.rootStore.ui.setNotification({
502-
category: 'success',
503-
message: `Successfully updated column "${column.name}"`,
504-
})
503+
504+
if (!skipSuccessMessage) {
505+
this.rootStore.ui.setNotification({
506+
category: 'success',
507+
message: `Successfully updated column "${column.name}"`,
508+
})
509+
}
505510
} catch (error: any) {
506511
return { error }
507512
}
@@ -789,12 +794,14 @@ export default class MetaStore implements IMetaStore {
789794
message: `Updating column ${column.name} from ${updatedTable.name}`,
790795
})
791796
const skipPKCreation = true
797+
const skipSuccessMessage = true
792798
const res: any = await this.updateColumn(
793799
column.id,
794800
columnPayload,
795801
updatedTable,
796802
column.foreignKey,
797-
skipPKCreation
803+
skipPKCreation,
804+
skipSuccessMessage
798805
)
799806
if (res?.error) {
800807
hasError = true

0 commit comments

Comments
 (0)