-
Notifications
You must be signed in to change notification settings - Fork 22
feat: implement cascading deletion for related records in delete #488
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
ca65f67
bda166a
12d2ba6
02e216b
23d178b
8b5b7b5
01dfcfd
ee04911
96c2c8f
a300f83
8ef2973
552ecdc
423d6a0
ff63b6c
9520f80
d6502d3
1843641
21cc9a4
81fce83
ec17bd5
a48d0a7
c3a7a73
b4d8aa1
604c0b3
b3d3f24
25c0007
2ad14c6
2ed72a3
aa3b223
067b87c
058283c
b0acb77
0c28370
17ac6d4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -126,7 +126,7 @@ export async function interpretResource( | |
| export default class AdminForthRestAPI implements IAdminForthRestAPI { | ||
|
|
||
| adminforth: IAdminForth; | ||
|
|
||
| constructor(adminforth: IAdminForth) { | ||
| this.adminforth = adminforth; | ||
| } | ||
|
|
@@ -152,6 +152,47 @@ export default class AdminForthRestAPI implements IAdminForthRestAPI { | |
| } | ||
| } | ||
| } | ||
| async deleteWithCascade(resource: AdminForthResource, primaryKey: string, context: {adminUser: any, response: any}): Promise<{ error: string | null }> { | ||
| const { adminUser, response } = context; | ||
|
|
||
| const record = await this.adminforth.connectors[resource.dataSource].getRecordByPrimaryKey(resource, primaryKey); | ||
|
|
||
| if (!record){ | ||
| return {error: `Record with id ${primaryKey} not found`}; | ||
| } | ||
|
|
||
| const childResources = this.adminforth.config.resources.filter(r =>r.columns.some(c => c.foreignResource?.resourceId === resource.resourceId)); | ||
|
|
||
| for (const childRes of childResources) { | ||
| const foreignColumn = childRes.columns.find(c => c.foreignResource?.resourceId === resource.resourceId); | ||
|
|
||
| if (!foreignColumn?.foreignResource?.onDelete) continue; | ||
|
|
||
| const strategy = foreignColumn.foreignResource.onDelete; | ||
|
|
||
| const childRecords = await this.adminforth.resource(childRes.resourceId).list(Filters.EQ(foreignColumn.name, primaryKey)); | ||
|
|
||
| const childPk = childRes.columns.find(c => c.primaryKey)?.name; | ||
| if (!childPk) continue; | ||
|
|
||
| if (strategy === 'cascade') { | ||
| for (const childRecord of childRecords) { | ||
| const childResult = await this.deleteWithCascade(childRes, childRecord[childPk], context); | ||
| if (childResult?.error) { | ||
| return childResult; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| if (strategy === 'setNull') { | ||
| for (const childRecord of childRecords) { | ||
| await this.adminforth.resource(childRes.resourceId).update(childRecord[childPk], {[foreignColumn.name]: null}); | ||
| } | ||
| } | ||
| } | ||
| const deleteResult = await this.adminforth.deleteResourceRecord({resource, record, adminUser, recordId: primaryKey, response}); | ||
| return { error: deleteResult.error}; | ||
| } | ||
|
|
||
| registerEndpoints(server: IHttpServer) { | ||
| server.endpoint({ | ||
|
|
@@ -1481,6 +1522,11 @@ export default class AdminForthRestAPI implements IAdminForthRestAPI { | |
| return { error }; | ||
| } | ||
|
|
||
| const { error: cascadeError } = await this.deleteWithCascade(resource, body.primaryKey, {adminUser, response}); | ||
| if (cascadeError) { | ||
| return { error: cascadeError }; | ||
| } | ||
|
|
||
| const { error: deleteError } = await this.adminforth.deleteResourceRecord({ | ||
| resource, record, adminUser, recordId: body['primaryKey'], response, | ||
|
Comment on lines
1523
to
1531
|
||
| extra: { body, query, headers, cookies, requestUrl, response } | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
SQLite's
PRAGMA foreign_key_list(tableName)returns the FK constraints defined intableName— i.e., wheretableNameis the child/dependent table. SofkMap[fk.from]maps columns in the current table that reference other (parent) tables, and checks if they have ON DELETE CASCADE.However, the warning aims to detect conflicts where a child table targeting the current (parent) resource already has a DB-level ON DELETE CASCADE — which would double-delete when adminForth's cascade deletion also runs. To detect that conflict correctly, you need to check tables that reference the current table as their parent (the reverse direction).
In contrast, the PostgreSQL and MySQL queries correctly filter by the current
tableNameas the referenced/parent table and find child tables that cascade-delete on the current table's row deletion.The SQLite check should use a different approach to detect child tables that reference the current table with ON DELETE CASCADE (e.g., check
PRAGMA foreign_key_liston all other discovered tables and look for references to the current table).