fix: address 4 P2 review issues from PR #8 round 6

db/commands: paginate tag search — add offset + COUNT query to
search_images_by_tag returning (images, total); TagSearchParams gains
offset field and command returns TagSearchPage; store handles both the
initial load and load-more path so results beyond PAGE_SIZE are reachable

commands: semantic search over-fetches by 4× when any post-query filter
(folder, media kind, favorites, rating) is active, then truncates to the
requested limit; previously well-rated matches ranked just beyond the bare
vector limit were silently omitted

db: wrap update_folder_path in a transaction so a path-rewrite failure
(e.g. uniqueness collision) rolls back the already-updated folder row
instead of leaving the DB in an inconsistent half-migrated state

store: invalidate exploreTagsFolderId when a tagging run completes so
Explore reflects the updated tag distribution without requiring a manual
folder switch; previously the cache was never busted by background tagging
This commit is contained in:
2026-06-07 22:15:48 +01:00
parent 04ade3d5d6
commit 7efb187971
3 changed files with 83 additions and 25 deletions
+27 -7
View File
@@ -1385,7 +1385,10 @@ pub fn rename_folder(conn: &Connection, folder_id: i64, new_name: &str) -> Resul
}
pub fn update_folder_path(conn: &Connection, folder_id: i64, old_path: &str, new_path: &str, new_name: &str) -> Result<()> {
conn.execute(
// Both updates must be atomic: if the image path rewrite fails (e.g. a
// uniqueness collision) the folder row must not remain at the new location.
let tx = conn.unchecked_transaction()?;
tx.execute(
"UPDATE folders SET path = ?2, name = ?3, scan_error = NULL WHERE id = ?1",
params![folder_id, new_path, new_name],
)?;
@@ -1393,10 +1396,11 @@ pub fn update_folder_path(conn: &Connection, folder_id: i64, old_path: &str, new
// re-generating thumbnails and embeddings for unchanged files.
// Use SUBSTR to replace only the leading prefix; SQLite's replace()
// would corrupt paths where the old folder name also appears deeper in the tree.
conn.execute(
tx.execute(
"UPDATE images SET path = ?2 || SUBSTR(path, LENGTH(?1) + 1) WHERE folder_id = ?3",
params![old_path, new_path, folder_id],
)?;
tx.commit()?;
Ok(())
}
@@ -1522,13 +1526,28 @@ pub fn search_images_by_tag(
favorites_only: bool,
rating_min: i64,
limit: usize,
) -> Result<Vec<ImageRecord>> {
offset: usize,
) -> Result<(Vec<ImageRecord>, usize)> {
let normalized_query = query.trim().to_ascii_lowercase();
if normalized_query.is_empty() {
return Ok(Vec::new());
return Ok((Vec::new(), 0));
}
let favorites_flag = i64::from(favorites_only);
// Total count (for pagination)
let total: usize = conn.query_row(
"SELECT COUNT(DISTINCT i.id)
FROM images i
JOIN image_tags t ON t.image_id = i.id
WHERE (?1 IS NULL OR i.folder_id = ?1)
AND (?2 IS NULL OR i.media_kind = ?2)
AND (?3 = 0 OR i.favorite = 1)
AND i.rating >= ?4
AND LOWER(TRIM(t.tag)) = ?5",
params![folder_id, media_kind, favorites_flag, rating_min, normalized_query],
|row| row.get::<_, i64>(0),
)? as usize;
let mut stmt = conn.prepare(
"SELECT DISTINCT i.id
FROM images i
@@ -1539,7 +1558,7 @@ pub fn search_images_by_tag(
AND i.rating >= ?4
AND LOWER(TRIM(t.tag)) = ?5
ORDER BY i.rating DESC, i.modified_at DESC NULLS LAST, i.filename ASC
LIMIT ?6",
LIMIT ?6 OFFSET ?7",
)?;
let image_ids = stmt
@@ -1550,13 +1569,14 @@ pub fn search_images_by_tag(
favorites_flag,
rating_min,
normalized_query,
limit as i64
limit as i64,
offset as i64
],
|row| row.get::<_, i64>(0),
)?
.collect::<rusqlite::Result<Vec<_>>>()?;
get_images_by_ids(conn, &image_ids)
Ok((get_images_by_ids(conn, &image_ids)?, total))
}
pub fn search_tags_autocomplete(