Skip to content

Repo-wide: apotheke UPDATE/DELETE repo functions silently succeed on zero-row match #492

Description

@forkwright

Finding

The pattern fixed for the stats/user store in #450 (a repo UPDATE/DELETE that ignores rows_affected() and returns Ok(()) even when zero rows matched) recurs across the apotheke media-entity and quality repos that were outside that cluster's scope. A caller cannot distinguish "updated" from "no such row", so a stale or wrong id silently no-ops.

Evidence

Surfaced during the #450 sweep. The same unchecked write pattern appears in crates/apotheke/src/repo/ across audiobook.rs, book.rs, comic.rs, movie.rs, music.rs (four entity levels each), news.rs, podcast.rs, and quality.rs (update_profile / delete_profile) — roughly 20+ functions. #450 fixed user.rs, renderer.rs, and the play-history/streak paths; these remain.

Why this matters

Silent no-op writes hide bugs and make PATCH/DELETE endpoints built on them return success for operations that did nothing — the same class the zone.rs delete_zone/remove_member pattern (the canonical fix) was written to prevent. It is a correctness and API-honesty gap that spans the crate.

Desired correction

Apply the capture-rows_affected-then-NotFound-on-zero pattern uniformly to the remaining entity and quality repo functions (excluding genuinely-bulk user-scoped operations where zero rows is legitimate, e.g. revoke_api_keys_for_user). Consider a basanos/kanon lint rule that flags a repo UPDATE/DELETE whose QueryResult is discarded, so the class cannot regrow.

Done when: every single-row apotheke UPDATE/DELETE reports NotFound on a zero-row match (or is explicitly documented as an idempotent bulk op), with tests; a lint guards the pattern.

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions