perf(#12): push suggest() filtering + LIMIT to SQL

Replace the in-memory approach (list_files → filter in Rust) with
SQL-level ILIKE + relevance ORDER BY + LIMIT.

- Add suggest_files_by_name() to FileReadPort (default impl for stubs)
- Add suggest_folders_by_name() to FolderRepository (default impl)
- Implement efficient SQL in FileBlobReadRepository & FolderDbRepository
  with LOWER(name) LIKE pattern, 3-tier relevance sorting, and LIMIT
- Rewrite SearchService::suggest() to call new methods in parallel

Before: 50K files → ~10 MB transferred, 52K string comparisons
After:  50K files → ~20 rows transferred, index-assisted scan
This commit is contained in:
Dionisio
2026-02-23 23:36:38 +01:00
parent ab0c4476b3
commit 44113eabdc
5 changed files with 240 additions and 39 deletions
+23
View File
@@ -125,6 +125,29 @@ pub trait FileReadPort: Send + Sync + 'static {
criteria: &SearchCriteriaDto,
user_id: &str,
) -> Result<usize, DomainError>;
/// Return up to `limit` files whose name contains `query` (case-insensitive).
///
/// Results are ordered by relevance (exact > starts-with > contains) so the
/// caller can use them directly for autocomplete suggestions.
///
/// The default implementation falls back to `list_files` + in-memory filter
/// so that stubs and mocks compile without changes.
async fn suggest_files_by_name(
&self,
folder_id: Option<&str>,
query: &str,
limit: usize,
) -> Result<Vec<File>, DomainError> {
let all = self.list_files(folder_id).await?;
let q = query.to_lowercase();
let mut matched: Vec<File> = all
.into_iter()
.filter(|f| f.name().to_lowercase().contains(&q))
.collect();
matched.truncate(limit);
Ok(matched)
}
}
// ─────────────────────────────────────────────────────