duplicates: library-aware visibility — only hide a demoted row when its survivor is reachable

Soft-marked rows used to disappear from /photos globally, including
from a library-scoped view that didn't contain the survivor at all.
A user browsing lib A who'd promoted a file from lib B as the
survivor would silently lose visibility on their own copy in lib A,
even though lib B's file isn't reachable from lib A's view.

Library-scoped queries now keep a demoted row visible when its
survivor lives in a library outside the current scope. Implemented
as a NOT EXISTS subquery against the same image_exif table aliased
as `survivor`. The unscoped (all-libraries) view is unchanged — every
survivor is reachable, so demoted rows stay hidden as before.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Cameron Cordes
2026-05-03 18:24:07 -04:00
parent 98057c98a1
commit 57b7bad086

View File

@@ -1222,7 +1222,38 @@ impl ExifDao for SqliteExifDao {
} }
if !include_duplicates { if !include_duplicates {
if library_ids.is_empty() {
// Unscoped (all-libraries) view — every survivor is
// reachable somewhere, so a soft-marked row is
// genuinely a duplicate from the user's perspective.
// Hide it.
query = query.filter(duplicate_of_hash.is_null()); query = query.filter(duplicate_of_hash.is_null());
} else {
// Scoped to specific libraries: only hide a
// soft-marked row when the survivor is reachable
// *in this view*. If the survivor lives in a
// library the user can't see right now, the
// demoted file is the only copy of those bytes
// they have access to — keep it visible.
//
// Implemented as a correlated NOT EXISTS subquery
// over an aliased image_exif. Library ids are i32
// so format!-inlining the integer list is safe.
use diesel::sql_types::Bool;
let lib_list = library_ids
.iter()
.map(i32::to_string)
.collect::<Vec<_>>()
.join(",");
let raw = format!(
"(image_exif.duplicate_of_hash IS NULL OR NOT EXISTS \
(SELECT 1 FROM image_exif AS survivor \
WHERE survivor.content_hash = image_exif.duplicate_of_hash \
AND survivor.library_id IN ({})))",
lib_list
);
query = query.filter(diesel::dsl::sql::<Bool>(&raw));
}
} }
query query