From 0da0e47784f345dd06d3254181c5838279f6c8c9 Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Sun, 12 Jul 2026 12:22:25 -0400 Subject: [PATCH] fix(wip-title): count inserts via pre-SELECT, not driver rowcount apply_wip_image_tags relied on result.rowcount, but psycopg reports -1 for a multi-row INSERT ... ON CONFLICT DO NOTHING (executemany path), so the return count (and the backfill's reported total) was wrong. Compute the count from a pre-SELECT of already-tagged ids within the same transaction; keep ON CONFLICT DO NOTHING as a race-safety belt. Co-Authored-By: Claude Opus 4.8 (1M context) --- backend/app/services/wip_title.py | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/backend/app/services/wip_title.py b/backend/app/services/wip_title.py index 4197486..7ad13d4 100644 --- a/backend/app/services/wip_title.py +++ b/backend/app/services/wip_title.py @@ -65,22 +65,37 @@ def resolve_wip_tag_id(session: Session) -> int | None: def apply_wip_image_tags(session: Session, image_ids, tag_id: int) -> int: - """Attach ``tag_id`` (source='wip_title') to each image id, idempotently - (ON CONFLICT DO NOTHING — never disturbs an existing tag or its source). - Returns the number of image_tag rows newly inserted. Does NOT commit.""" + """Attach ``tag_id`` (source='wip_title') to each image id, idempotently — + never disturbs an existing tag or its source. Returns the number of image_tag + rows newly inserted. Does NOT commit. + + The insert count is computed from a pre-SELECT of already-tagged ids rather + than the statement's ``rowcount``: psycopg reports -1 for a multi-row + ON CONFLICT DO NOTHING insert (it runs via an executemany path), so rowcount + is unusable here. The SELECT is accurate within this single transaction (no + concurrent writer touches these (image, wip) rows); ON CONFLICT DO NOTHING + stays as a race-safety belt so a rare concurrent insert can't error.""" ids = list({int(i) for i in image_ids}) if not ids: return 0 inserted = 0 for start in range(0, len(ids), _INSERT_CHUNK): chunk = ids[start:start + _INSERT_CHUNK] - result = session.execute( + already = set(session.execute( + select(image_tag.c.image_record_id) + .where(image_tag.c.tag_id == tag_id) + .where(image_tag.c.image_record_id.in_(chunk)) + ).scalars()) + to_insert = [iid for iid in chunk if iid not in already] + if not to_insert: + continue + session.execute( pg_insert(image_tag) .values([ {"image_record_id": iid, "tag_id": tag_id, "source": WIP_TITLE_SOURCE} - for iid in chunk + for iid in to_insert ]) .on_conflict_do_nothing(index_elements=["image_record_id", "tag_id"]) ) - inserted += result.rowcount or 0 + inserted += len(to_insert) return inserted