"""post_attachment: per-post sha uniqueness (empty-post flood fix) Revision ID: 0043 Revises: 0042 Create Date: 2026-06-08 PostAttachment.sha256 was GLOBALLY unique, so a non-art file the creator attaches to many posts (a standard pdf/zip/link-card) only ever got ONE row — on the first post — leaving every later post a bare shell (no image, no attachment). The native Patreon backfill of Anduo surfaced 1589 such shells (operator-flagged 2026-06-08). Switch to PER-POST uniqueness: the on-disk blob stays sha-deduped, but each post gets its own row. Replace the unique sha256 index with a plain lookup index plus two partial uniques — (post_id, sha256) for real posts and (sha256) for the NULL-post filesystem case (still one row per file there). Existing data has ≤1 row per sha (the old global unique), so the new partial uniques can't be violated on upgrade — no data backfill needed here. The bare-post shells themselves are removed by the separate prune-empty-posts cleanup tool. """ from typing import Sequence, Union import sqlalchemy as sa from alembic import op revision: str = "0043" down_revision: Union[str, None] = "0042" branch_labels: Union[str, Sequence[str], None] = None depends_on: Union[str, Sequence[str], None] = None def upgrade() -> None: # Drop the global unique index; recreate it as a plain (non-unique) lookup # index so sha-based reads keep their index (matches the model's index=True). op.drop_index("ix_post_attachment_sha256", table_name="post_attachment") op.create_index( "ix_post_attachment_sha256", "post_attachment", ["sha256"], ) op.create_index( "uq_post_attachment_post_sha", "post_attachment", ["post_id", "sha256"], unique=True, postgresql_where=sa.text("post_id IS NOT NULL"), ) op.create_index( "uq_post_attachment_null_post_sha", "post_attachment", ["sha256"], unique=True, postgresql_where=sa.text("post_id IS NULL"), ) def downgrade() -> None: op.drop_index( "uq_post_attachment_null_post_sha", table_name="post_attachment" ) op.drop_index( "uq_post_attachment_post_sha", table_name="post_attachment" ) op.drop_index("ix_post_attachment_sha256", table_name="post_attachment") op.create_index( "ix_post_attachment_sha256", "post_attachment", ["sha256"], unique=True, )