"""add nullable user_id to tenant-scoped tables + stock_bans_v2 (multi-tenant phase 1) Revision ID: 0010 Revises: 0009 Create Date: 2026-09-21 """ from alembic import op import sqlalchemy as sa # revision identifiers, used by Alembic. revision = "0010" down_revision = "0009" branch_labels = None depends_on = None # (table, index_name) _USER_ID_TABLES = [ "portfolio", "trade_history", "active_trades", "trade_journals", "trading_sessions", "mcp_tool_calls", "claude_analysis_log", ] def _has_column(conn, table: str, column: str) -> bool: return conn.execute(sa.text( "SELECT 1 FROM information_schema.columns WHERE table_name=:t AND column_name=:c" ), {"t": table, "c": column}).fetchone() is not None def _has_table(conn, table: str) -> bool: return conn.execute(sa.text( "SELECT 1 FROM information_schema.tables WHERE table_name=:t" ), {"t": table}).fetchone() is not None def upgrade() -> None: conn = op.get_bind() for table in _USER_ID_TABLES: if not _has_table(conn, table): continue # create_all 이전 스키마 없음 — 스킵(다음 실행에서 재시도) if _has_column(conn, table, "user_id"): continue # create_all 로 이미 생성됨 op.add_column( table, sa.Column("user_id", sa.Integer(), sa.ForeignKey("users.id", ondelete="SET NULL"), nullable=True), ) op.create_index(f"ix_{table}_user_id", table, ["user_id"]) if not _has_table(conn, "stock_bans_v2"): op.create_table( "stock_bans_v2", sa.Column("id", sa.Integer(), primary_key=True), sa.Column("user_id", sa.Integer(), sa.ForeignKey("users.id", ondelete="CASCADE"), nullable=False), sa.Column("stock_code", sa.String(20), nullable=False), sa.Column("consecutive_losses", sa.Integer(), nullable=False, server_default="0"), sa.Column("ban_until", sa.DateTime(), nullable=True), sa.Column("updated_at", sa.DateTime(), nullable=True), ) op.create_index("ix_stock_bans_v2_user_id", "stock_bans_v2", ["user_id"]) op.create_index("ix_stock_bans_v2_stock_code", "stock_bans_v2", ["stock_code"]) op.create_unique_constraint( "uq_stock_bans_v2_user_stock", "stock_bans_v2", ["user_id", "stock_code"] ) def downgrade() -> None: conn = op.get_bind() if _has_table(conn, "stock_bans_v2"): op.drop_table("stock_bans_v2") for table in _USER_ID_TABLES: if _has_table(conn, table) and _has_column(conn, table, "user_id"): op.drop_index(f"ix_{table}_user_id", table_name=table) op.drop_column(table, "user_id")