"""Match 表补充 CHECK 约束:完赛必须有比分 + 状态枚举 + 半场≤全场 Revision ID: 0018_match_checks Revises: 0017_mode_baseline Create Date: 2026-09-21 Code Review DB-5: - 已完赛比赛必须有比分(数据库级兜底) - match_status 枚举约束 - 半场进球 ≤ 全场进球 """ from typing import Sequence, Union from alembic import op # revision identifiers, used by Alembic. revision: str = '0018_match_checks' down_revision: Union[str, None] = '0017_mode_baseline' branch_labels: Union[str, Sequence[str], None] = None depends_on: Union[str, Sequence[str], None] = None def upgrade() -> None: # 先清理可能违反新约束的数据 op.execute("UPDATE matches SET match_status = 'scheduled' WHERE match_status NOT IN ('finished', 'scheduled', 'in_play', 'paused', 'postponed', 'cancelled', 'suspended')") op.execute("UPDATE matches SET home_goals = 0, away_goals = 0 WHERE match_status = 'finished' AND (home_goals IS NULL OR away_goals IS NULL)") # 添加 CHECK 约束 op.create_check_constraint( 'ck_matches_finished_has_score', 'matches', "match_status <> 'finished' OR (home_goals IS NOT NULL AND away_goals IS NOT NULL)", ) op.create_check_constraint( 'ck_matches_status_enum', 'matches', "match_status IN ('finished', 'scheduled', 'in_play', 'paused', 'postponed', 'cancelled', 'suspended')", ) op.create_check_constraint( 'ck_matches_home_ht_le_full', 'matches', "home_ht_goals IS NULL OR home_goals IS NULL OR home_ht_goals <= home_goals", ) op.create_check_constraint( 'ck_matches_away_ht_le_full', 'matches', "away_ht_goals IS NULL OR away_goals IS NULL OR away_ht_goals <= away_goals", ) def downgrade() -> None: op.drop_constraint('ck_matches_away_ht_le_full', 'matches', type_='check') op.drop_constraint('ck_matches_home_ht_le_full', 'matches', type_='check') op.drop_constraint('ck_matches_status_enum', 'matches', type_='check') op.drop_constraint('ck_matches_finished_has_score', 'matches', type_='check')