fix(plex): stop /facets 500 on a metadata-poor library
A mirrored Shows library whose items have no genres stored `genres` as JSON `null` — SQLAlchemy's JSONB persists a Python None as a JSON scalar, not SQL NULL. /facets unnested it with jsonb_array_elements_text(), which raises "cannot extract elements from a scalar", so a single bare library 500'd the whole Plex module on open (the grid loads, the filter sidebar's facets call dies). - Guard the /facets genre unnest with jsonb_typeof(genres)='array', mirroring the guard people.py already applies to the same columns. - Persist the JSONB tag columns (genres/directors/cast_names/collection_keys on plex_items and plex_shows) with none_as_null=True, so an empty list round-trips as SQL NULL and self-heals on the next sync — closing the trap for any future unnest. Verified against the real prod row (DumaTV show, genres = JSON null): the unguarded query errors, the guarded one returns cleanly.
This commit is contained in:
+11
-8
@@ -1020,16 +1020,19 @@ class PlexShow(Base, TimestampMixin, UpdatedAtMixin):
|
||||
thumb_key: Mapped[str | None] = mapped_column(String(512)) # Plex thumb path (proxied, not stored)
|
||||
art_key: Mapped[str | None] = mapped_column(String(512))
|
||||
child_count: Mapped[int | None] = mapped_column(Integer) # seasons
|
||||
collection_keys: Mapped[list | None] = mapped_column(JSONB) # Plex collections this show is in
|
||||
collection_keys: Mapped[list | None] = mapped_column(JSONB(none_as_null=True)) # Plex collections this show is in
|
||||
added_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), index=True)
|
||||
# Filterable / orderable metadata mirrored from the show section listing (parallels PlexItem).
|
||||
rating: Mapped[float | None] = mapped_column(Float, index=True) # Plex audienceRating (~IMDb)
|
||||
content_rating: Mapped[str | None] = mapped_column(String(16), index=True)
|
||||
studio: Mapped[str | None] = mapped_column(String(255), index=True) # network/studio
|
||||
originally_available_at: Mapped[Date | None] = mapped_column(Date, index=True) # first air date
|
||||
genres: Mapped[list | None] = mapped_column(JSONB)
|
||||
directors: Mapped[list | None] = mapped_column(JSONB)
|
||||
cast_names: Mapped[list | None] = mapped_column(JSONB)
|
||||
# none_as_null: an empty tag list must persist as SQL NULL, not JSON `null`. Without it a Python
|
||||
# None round-trips as a JSON scalar that jsonb_array_elements_text() can't unnest ("cannot extract
|
||||
# elements from a scalar") — the trap a metadata-poor library sprang on /facets. Same on plex_items.
|
||||
genres: Mapped[list | None] = mapped_column(JSONB(none_as_null=True))
|
||||
directors: Mapped[list | None] = mapped_column(JSONB(none_as_null=True))
|
||||
cast_names: Mapped[list | None] = mapped_column(JSONB(none_as_null=True))
|
||||
people_text: Mapped[str | None] = mapped_column(Text) # cast+directors, folded into search_vector
|
||||
# Set once the show's FULL cast has been rich-fetched by the backfill; NULL = still the listing top-3.
|
||||
people_enriched_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
|
||||
@@ -1116,11 +1119,11 @@ class PlexItem(Base, TimestampMixin, UpdatedAtMixin):
|
||||
content_rating: Mapped[str | None] = mapped_column(String(16), index=True)
|
||||
studio: Mapped[str | None] = mapped_column(String(255), index=True)
|
||||
originally_available_at: Mapped[Date | None] = mapped_column(Date, index=True)
|
||||
genres: Mapped[list | None] = mapped_column(JSONB)
|
||||
directors: Mapped[list | None] = mapped_column(JSONB)
|
||||
cast_names: Mapped[list | None] = mapped_column(JSONB)
|
||||
genres: Mapped[list | None] = mapped_column(JSONB(none_as_null=True))
|
||||
directors: Mapped[list | None] = mapped_column(JSONB(none_as_null=True))
|
||||
cast_names: Mapped[list | None] = mapped_column(JSONB(none_as_null=True))
|
||||
# rating_keys of the Plex collections this movie belongs to (GIN — filter + card-strip siblings).
|
||||
collection_keys: Mapped[list | None] = mapped_column(JSONB)
|
||||
collection_keys: Mapped[list | None] = mapped_column(JSONB(none_as_null=True))
|
||||
# Cast + director names, space-joined — folded into search_vector (weight B) so the search box
|
||||
# finds titles by a person's name (and ranks them above summary-only mentions).
|
||||
people_text: Mapped[str | None] = mapped_column(Text)
|
||||
|
||||
Reference in New Issue
Block a user