"""story_image_intents: one primary per story + optional FK to assets Revision ID: 0009_si_constraints Revises: 0008_legacy_assets """ from typing import Sequence, Union import sqlalchemy as sa from alembic import op revision: str = "0009_si_constraints" down_revision: Union[str, Sequence[str], None] = "0008_legacy_assets" branch_labels: Union[str, Sequence[str], None] = None depends_on: Union[str, Sequence[str], None] = None def upgrade() -> None: bind = op.get_bind() bind.execute( sa.text( """ UPDATE story_image_intents SET asset_id = NULL WHERE asset_id IS NOT NULL AND NOT EXISTS (SELECT 1 FROM assets a WHERE a.id = story_image_intents.asset_id) """ ) ) # 去重:同一 story 多条 primary 时保留最新一条 bind.execute( sa.text( """ DELETE FROM story_image_intents WHERE id IN ( SELECT id FROM ( SELECT id, ROW_NUMBER() OVER ( PARTITION BY story_id ORDER BY created_at DESC NULLS LAST, id DESC ) AS rn FROM story_image_intents WHERE intent_role = 'primary' ) t WHERE rn > 1 ) """ ) ) op.create_foreign_key( "fk_story_image_intents_asset_id_assets", "story_image_intents", "assets", ["asset_id"], ["id"], ondelete="SET NULL", ) op.execute( """ CREATE UNIQUE INDEX IF NOT EXISTS uq_story_primary_image_intent ON story_image_intents (story_id) WHERE intent_role = 'primary' """ ) def downgrade() -> None: op.execute("DROP INDEX IF EXISTS uq_story_primary_image_intent") op.drop_constraint( "fk_story_image_intents_asset_id_assets", "story_image_intents", type_="foreignkey", )