From c8d3326cdc7b7f71ec580a286b5adcd7baa59d25 Mon Sep 17 00:00:00 2001 From: Dionisio Date: Thu, 26 Feb 2026 00:08:38 +0100 Subject: [PATCH] perf: replace recursive CTE with ltree <@ in delete_folder MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- .../repositories/pg/folder_db_repository.rs | 34 ++++++++----------- 1 file changed, 15 insertions(+), 19 deletions(-) diff --git a/src/infrastructure/repositories/pg/folder_db_repository.rs b/src/infrastructure/repositories/pg/folder_db_repository.rs index 624c5c24..7ff073a1 100644 --- a/src/infrastructure/repositories/pg/folder_db_repository.rs +++ b/src/infrastructure/repositories/pg/folder_db_repository.rs @@ -465,17 +465,15 @@ impl FolderRepository for FolderDbRepository { } async fn delete_folder(&self, id: &str) -> Result<(), DomainError> { - // First, delete all files in this folder and descendant folders - // to avoid constraint violations from ON DELETE SET NULL + // Delete all files whose folder is anywhere in the subtree. + // Uses the GiST-indexed ltree `<@` operator — O(log N) vs the + // O(depth × N) recursive CTE it replaces. sqlx::query( - r#" - WITH RECURSIVE descendants AS ( - SELECT id FROM storage.folders WHERE id = $1::uuid - UNION ALL - 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) - "#, + "DELETE FROM storage.files \ + WHERE folder_id IN ( \ + SELECT id FROM storage.folders \ + WHERE lpath <@ (SELECT lpath FROM storage.folders WHERE id = $1::uuid) \ + )", ) .bind(id) .execute(self.pool()) @@ -594,16 +592,14 @@ impl FolderRepository for FolderDbRepository { } 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( - r#" - WITH RECURSIVE descendants AS ( - SELECT id FROM storage.folders WHERE id = $1::uuid - UNION ALL - 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) - "#, + "DELETE FROM storage.files \ + WHERE folder_id IN ( \ + SELECT id FROM storage.folders \ + WHERE lpath <@ (SELECT lpath FROM storage.folders WHERE id = $1::uuid) \ + )", ) .bind(folder_id) .execute(self.pool())