"""saved_views.source (ask 4: per-module saved views) Saved "smart views" were feed-only; the Plex module now has them too. Add a `source` discriminator (default "feed" so every existing row stays a feed view) and widen the per-user unique name constraint to (user_id, source, name) so a feed view and a Plex view can share a name. `source` is NOT NULL with a server_default, so the ADD is a metadata-only rewrite-free change; the constraint swap is quick on a small per-user table. """ from typing import Sequence, Union import sqlalchemy as sa from alembic import op revision: str = "0073_saved_view_source" down_revision: Union[str, None] = "0072_plex_person_bio" branch_labels: Union[str, Sequence[str], None] = None depends_on: Union[str, Sequence[str], None] = None def upgrade() -> None: op.add_column( "saved_views", sa.Column("source", sa.String(length=16), nullable=False, server_default="feed"), ) op.drop_constraint("uq_user_saved_view", "saved_views", type_="unique") op.create_unique_constraint( "uq_user_source_saved_view", "saved_views", ["user_id", "source", "name"] ) def downgrade() -> None: # LOSSY / effectively forward-only: upgrade lets a user hold a feed view and a plex view of the same # name, so re-creating the narrow (user_id, name) constraint can raise IntegrityError on such a pair. # De-dup by dropping the non-feed rows first (they can't exist in the pre-0073 schema anyway) so the # narrow constraint re-applies cleanly for the drift test's downgrade→re-upgrade cycle. op.execute("DELETE FROM saved_views WHERE source <> 'feed'") op.drop_constraint("uq_user_source_saved_view", "saved_views", type_="unique") op.create_unique_constraint("uq_user_saved_view", "saved_views", ["user_id", "name"]) op.drop_column("saved_views", "source")