First real customers of the DB lane: - test_plex_facets: GET /api/plex/facets?scope=show returns 200 (not the "cannot extract elements from a scalar" 500) for a show whose genres is a JSONB scalar `null` — the 0.53.1 read-guard's regression test. Mutation-checked: removing the jsonb_typeof='array' guard turns it red with that exact error. - migration 0060 (S3a): one-time UPDATE …=NULL WHERE jsonb_typeof=… 'null' over the plex_items/plex_shows tag columns, so pre-0.53.1 JSONB-null rows become uniform SQL NULL and the read-guards go belt-only. test_plex_jsonb_normalize verifies scalar-null → SQL NULL while a real array is preserved. Gate: ruff clean; DB lane 176 passed; pure-logic lane unchanged (skips the DB tests).
35 lines
1.4 KiB
Python
35 lines
1.4 KiB
Python
"""normalize plex JSONB tag columns: scalar `null` -> SQL NULL (R6 S3a)
|
|
|
|
Pre-0.53.1 rows persisted a genre-less show/movie's genres/directors/cast_names/collection_keys as
|
|
a JSONB scalar `null` (SQLAlchemy wrote a Python None as JSON null before none_as_null=True). The
|
|
0.53.1 hotfix added none_as_null=True (new writes are SQL NULL) + jsonb_typeof read-guards, but the
|
|
existing rows stayed JSON `null`, which kept those read-guards load-bearing rather than belt-only.
|
|
|
|
This one-time data fixup rewrites those scalars to SQL NULL so the columns are uniform. Data-only,
|
|
forward-only: a SQL NULL is indistinguishable from the old JSON `null` to every reader (the guards
|
|
treat both as "not an array"), so there is nothing meaningful to downgrade. No schema change.
|
|
"""
|
|
from typing import Sequence, Union
|
|
|
|
from alembic import op
|
|
|
|
revision: str = "0060_plex_jsonb_null_normalize"
|
|
down_revision: Union[str, None] = "0059_dl_link_pw_version"
|
|
branch_labels: Union[str, Sequence[str], None] = None
|
|
depends_on: Union[str, Sequence[str], None] = None
|
|
|
|
_TABLES = ("plex_items", "plex_shows")
|
|
_COLS = ("genres", "directors", "cast_names", "collection_keys")
|
|
|
|
|
|
def upgrade() -> None:
|
|
for table in _TABLES:
|
|
for col in _COLS:
|
|
op.execute(
|
|
f"UPDATE {table} SET {col} = NULL WHERE jsonb_typeof({col}) = 'null'"
|
|
)
|
|
|
|
|
|
def downgrade() -> None:
|
|
pass # forward-only: SQL NULL == the old JSON null for every reader
|