"""Sessions and client events for the Sprint-0 parallel batch. Two tables that the parallel task batch (T3/T4) needs, landed together in one revision so no task ever writes a migration: 1. ``id_session`` — server-revocable refresh sessions (架构文档 §6.2, FR-ACC-05). 2. ``ob_event`` — client telemetry (架构文档 §6.2, FR-SYS-06). Partitioning (架构文档 says "按月分区") is deliberately deferred for both — same call as ``ob_ai_call``: plain tables until volume justifies it. Reversible: 是。降级直接 drop 两表。 """ from __future__ import annotations from collections.abc import Sequence import sqlalchemy as sa from alembic import op from sqlalchemy.dialects import postgresql revision: str = "0004" down_revision: str | None = "0003" branch_labels: str | Sequence[str] | None = None depends_on: str | Sequence[str] | None = None def upgrade() -> None: op.create_table( "id_session", sa.Column("session_id", sa.BigInteger(), nullable=False), sa.Column("user_id", sa.BigInteger(), nullable=False), sa.Column("refresh_token_hash", sa.String(length=64), nullable=False), sa.Column("expire_at", sa.DateTime(timezone=True), nullable=False), sa.Column("revoked", sa.Boolean(), nullable=False, server_default=sa.text("false")), sa.Column("revoked_at", sa.DateTime(timezone=True), nullable=True), sa.Column("created_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.func.now()), sa.PrimaryKeyConstraint("session_id"), sa.UniqueConstraint("refresh_token_hash", name="uq_id_session_token_hash"), ) op.create_index("ix_id_session_user", "id_session", ["user_id"]) op.create_table( "ob_event", sa.Column("event_id", sa.BigInteger(), nullable=False), sa.Column("user_id", sa.BigInteger(), nullable=False), sa.Column("event_name", sa.String(length=64), nullable=False), sa.Column("props", postgresql.JSONB(), nullable=False), sa.Column("client_ts", sa.DateTime(timezone=True), nullable=True), sa.Column("server_ts", sa.DateTime(timezone=True), nullable=False, server_default=sa.func.now()), sa.PrimaryKeyConstraint("event_id"), ) op.create_index("ix_ob_event_name_ts", "ob_event", ["event_name", "server_ts"]) op.create_index("ix_ob_event_user_ts", "ob_event", ["user_id", "server_ts"]) def downgrade() -> None: op.drop_index("ix_ob_event_user_ts", table_name="ob_event") op.drop_index("ix_ob_event_name_ts", table_name="ob_event") op.drop_table("ob_event") op.drop_index("ix_id_session_user", table_name="id_session") op.drop_table("id_session")