"""add account_id to PSA and notification tables Revision ID: 8aac5b372402 Revises: a1d2a84b9abb Create Date: 2026-04-09 00:00:00.000000 """ from typing import Sequence, Union from alembic import op import sqlalchemy as sa revision: str = '8aac5b372402' down_revision: Union[str, None] = 'a1d2a84b9abb' branch_labels: Union[str, Sequence[str], None] = None depends_on: Union[str, Sequence[str], None] = None def upgrade() -> None: # Step 1: ADD COLUMN for table in ('psa_post_log', 'psa_member_mappings', 'notification_logs'): op.add_column(table, sa.Column('account_id', sa.UUID(), nullable=True)) op.create_foreign_key( f'fk_{table}_account_id', table, 'accounts', ['account_id'], ['id'], ondelete='CASCADE', ) # Step 2: BACKFILL # psa_post_log: prefer psa_connection → fallback to posted_by user # Note: cannot reference the updated table (ppl) inside the FROM clause JOIN, # so use a correlated subquery for psa_connections lookup instead. op.execute(""" UPDATE psa_post_log ppl SET account_id = COALESCE( (SELECT account_id FROM psa_connections WHERE id = ppl.psa_connection_id), u.account_id ) FROM users u WHERE ppl.posted_by = u.id AND ppl.account_id IS NULL """) # psa_member_mappings: via psa_connection op.execute(""" UPDATE psa_member_mappings pmm SET account_id = pc.account_id FROM psa_connections pc WHERE pmm.psa_connection_id = pc.id AND pmm.account_id IS NULL """) # notification_logs: via notification_config op.execute(""" UPDATE notification_logs nl SET account_id = nc.account_id FROM notification_configs nc WHERE nl.notification_config_id = nc.id AND nl.account_id IS NULL """) # Step 3: VERIFY for table in ('psa_post_log', 'psa_member_mappings', 'notification_logs'): result = op.get_bind().execute( sa.text(f"SELECT COUNT(*) FROM {table} WHERE account_id IS NULL") ) count = result.scalar() if count > 0: raise RuntimeError(f"ROLLBACK: {count} NULL account_id rows in {table}.") # Step 4: SET NOT NULL for table in ('psa_post_log', 'psa_member_mappings', 'notification_logs'): op.alter_column(table, 'account_id', nullable=False) # Step 5: CREATE INDEX for table in ('psa_post_log', 'psa_member_mappings', 'notification_logs'): op.create_index(f'ix_{table}_account_id', table, ['account_id']) def downgrade() -> None: for table in ('psa_post_log', 'psa_member_mappings', 'notification_logs'): op.drop_index(f'ix_{table}_account_id', table_name=table) op.drop_constraint(f'fk_{table}_account_id', table, type_='foreignkey') op.drop_column(table, 'account_id')