From a712cef92d85a9982a79fd31d1ba5eef93176d69 Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Wed, 10 Jun 2026 20:29:42 -0400 Subject: [PATCH] fix(migration): 0045 backfill guards json_each against non-object rows Some image_record rows store tagger_predictions as a JSON scalar/null rather than an object; json_each throws 'cannot deconstruct a scalar' on those, rolling back the whole migration. Filter to json_typeof = 'object' in a MATERIALIZED CTE so the guard runs before json_each ever evaluates a scalar. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../versions/0045_image_prediction_table.py | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/alembic/versions/0045_image_prediction_table.py b/alembic/versions/0045_image_prediction_table.py index 189fb1a..de78507 100644 --- a/alembic/versions/0045_image_prediction_table.py +++ b/alembic/versions/0045_image_prediction_table.py @@ -55,17 +55,26 @@ def upgrade() -> None: # 0.70) right here so this is self-sufficient — it does NOT depend on the # #764 prune having run, and extracting only the >=floor tail keeps # image_prediction small (~tens of rows/image) even from the full JSON. + # MATERIALIZED so the json_typeof guard runs BEFORE json_each — some rows + # store tagger_predictions as a JSON scalar/null (not an object), and + # json_each throws "cannot deconstruct a scalar" on those. Filtering to + # objects in a materialized CTE keeps json_each from ever seeing them. op.execute( """ + WITH objs AS MATERIALIZED ( + SELECT id, tagger_predictions AS preds + FROM image_record + WHERE tagger_predictions IS NOT NULL + AND json_typeof(tagger_predictions) = 'object' + ) INSERT INTO image_prediction (image_record_id, raw_name, category, score) - SELECT ir.id, + SELECT o.id, je.key, COALESCE(je.value ->> 'category', 'general'), (je.value ->> 'confidence')::double precision - FROM image_record ir, - json_each(ir.tagger_predictions) je - WHERE ir.tagger_predictions IS NOT NULL - AND je.value ->> 'confidence' IS NOT NULL + FROM objs o, + json_each(o.preds) je + WHERE je.value ->> 'confidence' IS NOT NULL AND (je.value ->> 'confidence')::double precision >= COALESCE( (SELECT tagger_store_floor FROM ml_settings WHERE id = 1),