之前用独立 sa.ForeignKeyConstraint 报 ArgumentError, 改用 sa.Column(..., sa.ForeignKey(...), ...) 形式( 跟 model 里的 mapped_column 形式对齐)。
64 lines
1.7 KiB
Python
64 lines
1.7 KiB
Python
"""文章已读记录(per-user)
|
|
|
|
- article_reads.user_id INTEGER FK users.id ON DELETE CASCADE
|
|
- article_reads.article_id BIGINT FK articles.id ON DELETE CASCADE
|
|
- article_reads.read_at TIMESTAMPTZ DEFAULT now()
|
|
- 复合主键 (user_id, article_id) — 天然幂等
|
|
|
|
Revision ID: 0007
|
|
Revises: 0006
|
|
Create Date: 2026-06-13
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
from typing import Sequence, Union
|
|
|
|
import sqlalchemy as sa
|
|
from alembic import op
|
|
|
|
revision: str = "0007"
|
|
down_revision: Union[str, None] = "0006"
|
|
branch_labels = None
|
|
depends_on = None
|
|
|
|
|
|
def upgrade() -> None:
|
|
op.create_table(
|
|
"article_reads",
|
|
sa.Column(
|
|
"user_id",
|
|
sa.Integer,
|
|
sa.ForeignKey("users.id", ondelete="CASCADE"),
|
|
nullable=False,
|
|
),
|
|
sa.Column(
|
|
"article_id",
|
|
sa.BigInteger,
|
|
sa.ForeignKey("articles.id", ondelete="CASCADE"),
|
|
nullable=False,
|
|
),
|
|
sa.Column(
|
|
"read_at",
|
|
sa.DateTime(timezone=True),
|
|
nullable=False,
|
|
server_default=sa.text("now()"),
|
|
),
|
|
sa.PrimaryKeyConstraint("user_id", "article_id", name="pk_article_reads"),
|
|
)
|
|
op.create_index(
|
|
"ix_article_reads_user_read_at",
|
|
"article_reads",
|
|
["user_id", "read_at"],
|
|
)
|
|
op.create_index(
|
|
"ix_article_reads_article",
|
|
"article_reads",
|
|
["article_id"],
|
|
)
|
|
|
|
|
|
def downgrade() -> None:
|
|
op.drop_index("ix_article_reads_article", table_name="article_reads")
|
|
op.drop_index("ix_article_reads_user_read_at", table_name="article_reads")
|
|
op.drop_table("article_reads")
|