Optimize search service with database-level pagination and improve cache handling

- Add search_files_paginated method to FileReadPort for database-level pagination
- Implement efficient SQL-based search with LIMIT/OFFSET in file_blob_read_repository
- Add relevance scoring (exact match > starts-with > contains)
- Support multiple sort options (name, date, size) with ascending/descending order
- Fix cache expiration handling with proper borrow checker semantics
- Use i64 for SQL LIMIT/OFFSET parameters instead of usize
- Clean up duplicate SQL query builder code in search_files_paginated

This significantly improves search performance for non-recursive queries by:
- Pushing pagination to the database layer
- Avoiding loading all files into memory for filtering
- Supporting database-level sorting
This commit is contained in:
George Wu
2026-02-21 14:48:39 -08:00
parent 46a65c322c
commit 95fa648a55
2 changed files with 114 additions and 71 deletions
@@ -393,8 +393,8 @@ impl FileReadPort for FileBlobReadRepository {
criteria: &SearchCriteriaDto,
user_id: &str,
) -> Result<(Vec<File>, usize), DomainError> {
let offset = criteria.offset;
let limit = criteria.limit;
let offset = criteria.offset as i64;
let limit = criteria.limit as i64;
// Determine sort order
let (order_column, order_dir) = match criteria.sort_by.as_str() {
@@ -439,21 +439,23 @@ impl FileReadPort for FileBlobReadRepository {
Option<String>,
)> = sqlx::query_as(&format!(
"SELECT fi.id::text, fi.name, fi.folder_id::text, fo.path,
fi.size, fi.mime_type,
EXTRACT(EPOCH FROM fi.created_at)::bigint,
EXTRACT(EPOCH FROM fi.updated_at)::bigint,
fi.user_id::text
FROM storage.files fi
LEFT JOIN storage.folders fo ON fo.id = fi.folder_id
WHERE fi.user_id = $1::uuid AND fi.folder_id = $2::uuid
AND fi.is_trashed = false AND LOWER(fi.name) LIKE $3
ORDER BY {} {}
LIMIT {} OFFSET {}",
order_column, order_dir, limit, offset
fi.size, fi.mime_type,
EXTRACT(EPOCH FROM fi.created_at)::bigint,
EXTRACT(EPOCH FROM fi.updated_at)::bigint,
fi.user_id::text
FROM storage.files fi
LEFT JOIN storage.folders fo ON fo.id = fi.folder_id
WHERE fi.user_id = $1::uuid AND fi.folder_id = $2::uuid
AND fi.is_trashed = false AND LOWER(fi.name) LIKE $3
ORDER BY {} {}
LIMIT $4 OFFSET $5",
order_column, order_dir
))
.bind(user_id)
.bind(fid)
.bind(&name_pattern)
.bind(limit)
.bind(offset)
.fetch_all(self.pool.as_ref())
.await
.map_err(|e| DomainError::internal_error("FileBlobRead", format!("search: {e}")))?;
@@ -485,20 +487,22 @@ impl FileReadPort for FileBlobReadRepository {
Option<String>,
)> = sqlx::query_as(&format!(
"SELECT fi.id::text, fi.name, fi.folder_id::text, fo.path,
fi.size, fi.mime_type,
EXTRACT(EPOCH FROM fi.created_at)::bigint,
EXTRACT(EPOCH FROM fi.updated_at)::bigint,
fi.user_id::text
FROM storage.files fi
LEFT JOIN storage.folders fo ON fo.id = fi.folder_id
WHERE fi.user_id = $1::uuid AND fi.folder_id = $2::uuid
AND fi.is_trashed = false
ORDER BY {} {}
LIMIT {} OFFSET {}",
order_column, order_dir, limit, offset
fi.size, fi.mime_type,
EXTRACT(EPOCH FROM fi.created_at)::bigint,
EXTRACT(EPOCH FROM fi.updated_at)::bigint,
fi.user_id::text
FROM storage.files fi
LEFT JOIN storage.folders fo ON fo.id = fi.folder_id
WHERE fi.user_id = $1::uuid AND fi.folder_id = $2::uuid
AND fi.is_trashed = false
ORDER BY {} {}
LIMIT $3 OFFSET $4",
order_column, order_dir
))
.bind(user_id)
.bind(fid)
.bind(limit)
.bind(offset)
.fetch_all(self.pool.as_ref())
.await
.map_err(|e| DomainError::internal_error("FileBlobRead", format!("search: {e}")))?;
@@ -532,20 +536,22 @@ impl FileReadPort for FileBlobReadRepository {
Option<String>,
)> = sqlx::query_as(&format!(
"SELECT fi.id::text, fi.name, fi.folder_id::text, fo.path,
fi.size, fi.mime_type,
EXTRACT(EPOCH FROM fi.created_at)::bigint,
EXTRACT(EPOCH FROM fi.updated_at)::bigint,
fi.user_id::text
FROM storage.files fi
LEFT JOIN storage.folders fo ON fo.id = fi.folder_id
WHERE fi.user_id = $1::uuid AND fi.is_trashed = false
AND LOWER(fi.name) LIKE $2
ORDER BY {} {}
LIMIT {} OFFSET {}",
order_column, order_dir, limit, offset
fi.size, fi.mime_type,
EXTRACT(EPOCH FROM fi.created_at)::bigint,
EXTRACT(EPOCH FROM fi.updated_at)::bigint,
fi.user_id::text
FROM storage.files fi
LEFT JOIN storage.folders fo ON fo.id = fi.folder_id
WHERE fi.user_id = $1::uuid AND fi.is_trashed = false
AND LOWER(fi.name) LIKE $2
ORDER BY {} {}
LIMIT $3 OFFSET $4",
order_column, order_dir
))
.bind(user_id)
.bind(&name_pattern)
.bind(limit)
.bind(offset)
.fetch_all(self.pool.as_ref())
.await
.map_err(|e| DomainError::internal_error("FileBlobRead", format!("search: {e}")))?;
@@ -575,18 +581,20 @@ impl FileReadPort for FileBlobReadRepository {
Option<String>,
)> = sqlx::query_as(&format!(
"SELECT fi.id::text, fi.name, fi.folder_id::text, fo.path,
fi.size, fi.mime_type,
EXTRACT(EPOCH FROM fi.created_at)::bigint,
EXTRACT(EPOCH FROM fi.updated_at)::bigint,
fi.user_id::text
FROM storage.files fi
LEFT JOIN storage.folders fo ON fo.id = fi.folder_id
WHERE fi.user_id = $1::uuid AND fi.is_trashed = false
ORDER BY {} {}
LIMIT {} OFFSET {}",
order_column, order_dir, limit, offset
fi.size, fi.mime_type,
EXTRACT(EPOCH FROM fi.created_at)::bigint,
EXTRACT(EPOCH FROM fi.updated_at)::bigint,
fi.user_id::text
FROM storage.files fi
LEFT JOIN storage.folders fo ON fo.id = fi.folder_id
WHERE fi.user_id = $1::uuid AND fi.is_trashed = false
ORDER BY {} {}
LIMIT $2 OFFSET $3",
order_column, order_dir
))
.bind(user_id)
.bind(limit)
.bind(offset)
.fetch_all(self.pool.as_ref())
.await
.map_err(|e| DomainError::internal_error("FileBlobRead", format!("search: {e}")))?;