perf: replace recursive CTE with ltree <@ in delete_folder
Both delete_folder and delete_folder_permanently used WITH RECURSIVE
to find descendant folders before deleting their files. This scans
the parent_id chain row-by-row (O(depth × N rows)).
Replace with:
DELETE FROM storage.files
WHERE folder_id IN (
SELECT id FROM storage.folders
WHERE lpath <@ (SELECT lpath FROM storage.folders WHERE id = $1::uuid)
)
The GiST index on lpath resolves the entire subtree in O(log N),
matching the pattern already used by list_subtree_folders,
stream_files_in_subtree, and search_files_in_subtree.
This commit is contained in:
@@ -465,17 +465,15 @@ impl FolderRepository for FolderDbRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn delete_folder(&self, id: &str) -> Result<(), DomainError> {
|
async fn delete_folder(&self, id: &str) -> Result<(), DomainError> {
|
||||||
// First, delete all files in this folder and descendant folders
|
// Delete all files whose folder is anywhere in the subtree.
|
||||||
// to avoid constraint violations from ON DELETE SET NULL
|
// Uses the GiST-indexed ltree `<@` operator — O(log N) vs the
|
||||||
|
// O(depth × N) recursive CTE it replaces.
|
||||||
sqlx::query(
|
sqlx::query(
|
||||||
r#"
|
"DELETE FROM storage.files \
|
||||||
WITH RECURSIVE descendants AS (
|
WHERE folder_id IN ( \
|
||||||
SELECT id FROM storage.folders WHERE id = $1::uuid
|
SELECT id FROM storage.folders \
|
||||||
UNION ALL
|
WHERE lpath <@ (SELECT lpath FROM storage.folders WHERE id = $1::uuid) \
|
||||||
SELECT f.id FROM storage.folders f JOIN descendants d ON f.parent_id = d.id
|
)",
|
||||||
)
|
|
||||||
DELETE FROM storage.files WHERE folder_id IN (SELECT id FROM descendants)
|
|
||||||
"#,
|
|
||||||
)
|
)
|
||||||
.bind(id)
|
.bind(id)
|
||||||
.execute(self.pool())
|
.execute(self.pool())
|
||||||
@@ -594,16 +592,14 @@ impl FolderRepository for FolderDbRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn delete_folder_permanently(&self, folder_id: &str) -> Result<(), DomainError> {
|
async fn delete_folder_permanently(&self, folder_id: &str) -> Result<(), DomainError> {
|
||||||
// First, delete all files in this folder and descendant folders
|
// Delete all files whose folder is anywhere in the subtree
|
||||||
|
// (GiST ltree index, same pattern as delete_folder).
|
||||||
sqlx::query(
|
sqlx::query(
|
||||||
r#"
|
"DELETE FROM storage.files \
|
||||||
WITH RECURSIVE descendants AS (
|
WHERE folder_id IN ( \
|
||||||
SELECT id FROM storage.folders WHERE id = $1::uuid
|
SELECT id FROM storage.folders \
|
||||||
UNION ALL
|
WHERE lpath <@ (SELECT lpath FROM storage.folders WHERE id = $1::uuid) \
|
||||||
SELECT f.id FROM storage.folders f JOIN descendants d ON f.parent_id = d.id
|
)",
|
||||||
)
|
|
||||||
DELETE FROM storage.files WHERE folder_id IN (SELECT id FROM descendants)
|
|
||||||
"#,
|
|
||||||
)
|
)
|
||||||
.bind(folder_id)
|
.bind(folder_id)
|
||||||
.execute(self.pool())
|
.execute(self.pool())
|
||||||
|
|||||||
Reference in New Issue
Block a user