"""Allow walletless Telegram bindings. Before: every binding had to be tied to a subscribed wallet. After: anyone who DMs the bot can subscribe to public alerts; the wallet_address column becomes nullable. Uniqueness is on chat_id alone — the same Telegram chat can never have two bindings. A wallet can still have at most one binding (enforced by a partial unique index on wallet_address WHERE wallet_address IS NOT NULL). Revision ID: 021 Revises: 020 Create Date: 2026-05-24 """ from alembic import op import sqlalchemy as sa revision = "021" down_revision = "020" branch_labels = None depends_on = None def upgrade() -> None: # SQLite + batch mode → recreates the table preserving rows. with op.batch_alter_table("telegram_bindings") as batch: batch.alter_column("wallet_address", existing_type=sa.String(64), nullable=True) # Drop the old unique constraint on wallet_address (was created via # `unique=True`), then add a partial unique index that allows multiple # NULL wallets but still prevents two bindings for the same wallet. # SQLite quirk: the auto-generated unique index name is # `ix_telegram_bindings_wallet_address` (because we also indexed it). # We replace it with the partial-unique form. with op.batch_alter_table("telegram_bindings") as batch: batch.drop_index("ix_telegram_bindings_wallet_address") op.create_index( "ix_telegram_bindings_wallet_address", "telegram_bindings", ["wallet_address"], unique=True, sqlite_where=sa.text("wallet_address IS NOT NULL"), postgresql_where=sa.text("wallet_address IS NOT NULL"), ) def downgrade() -> None: # Note: downgrade FAILS if any walletless rows exist — caller must # delete them first. This is intentional (data preservation > graceful # downgrade for an opt-in safety net). op.drop_index("ix_telegram_bindings_wallet_address", table_name="telegram_bindings") with op.batch_alter_table("telegram_bindings") as batch: batch.alter_column("wallet_address", existing_type=sa.String(64), nullable=False) batch.create_index("ix_telegram_bindings_wallet_address", ["wallet_address"], unique=True)