This reverts 2529b51. Not a retreat — a reordering, on the operator's
call, and the better sequence.
The squash's acceptance test (run 4971) found ~130 places where the ORM
models do not describe the deployed schema (#3275), including a
unique=True the database never had and two UNIQUE indexes that exist
only in migrations. Collapsing now would have baked all of that into the
one file a public installer starts from.
So: fix the drift first as ordinary migrations on the intact chain, let
the operator deploy so their database moves to the corrected head, and
only then collapse. The baseline is then generated from reconciled
models and reproduces a schema worth reproducing.
Nothing is lost by reverting. The baseline was never deployed, and
regenerating it after the fixes is strictly better than patching this
copy — it will come out of autogenerate correct rather than needing the
same hand-finishing twice.
63 lines
2.3 KiB
Python
63 lines
2.3 KiB
Python
"""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,
|
|
)
|