| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130 |
- """Q3业务规则对齐、角色收敛和正文检索字段。
- Revision ID: 0003_q3_business_alignment_and_content_search
- Revises: 0002_add_restore_audit_action
- Create Date: 2026-08-07
- """
- from __future__ import annotations
- from collections.abc import Sequence
- from alembic import op
- import sqlalchemy as sa
- from sqlalchemy.dialects import mysql
- revision: str = "0003_q3_business_alignment_and_content_search"
- down_revision: str | None = "0002_add_restore_audit_action"
- branch_labels: str | Sequence[str] | None = None
- depends_on: str | Sequence[str] | None = None
- ROLE_CONSTRAINT = "user_role_code"
- CONTENT_STATUS_CONSTRAINT = "document_content_extract_status"
- def upgrade() -> None:
- # 本阶段按契约使用可读revision名,长度超过Alembic默认VARCHAR(32)。
- op.alter_column(
- "alembic_version",
- "version_num",
- existing_type=sa.String(length=32),
- type_=sa.String(length=128),
- existing_nullable=False,
- )
- op.add_column(
- "doc_document",
- sa.Column("content_text", mysql.LONGTEXT(), nullable=True),
- )
- op.add_column(
- "doc_document",
- sa.Column(
- "content_extract_status",
- sa.String(length=32),
- server_default=sa.text("'UNSUPPORTED'"),
- nullable=False,
- ),
- )
- op.add_column(
- "doc_document",
- sa.Column("content_extracted_at", mysql.DATETIME(fsp=3), nullable=True),
- )
- op.alter_column(
- "doc_document",
- "search_text",
- existing_type=sa.Text(),
- type_=mysql.LONGTEXT(),
- existing_nullable=True,
- )
- op.create_check_constraint(
- CONTENT_STATUS_CONSTRAINT,
- "doc_document",
- "content_extract_status IN "
- "('SUCCESS', 'EMPTY', 'UNSUPPORTED', 'FAILED')",
- )
- # 历史审计员必须先使全部旧Token失效,再禁用并逻辑删除;绝不升级为管理员。
- op.execute(
- sa.text(
- "UPDATE sys_user SET "
- "auth_version = auth_version + 1, "
- "row_version = row_version + 1, "
- "status = 'DISABLED', "
- "is_deleted = 1, "
- "deleted_at = COALESCE(deleted_at, CURRENT_TIMESTAMP(3)), "
- "updated_at = CURRENT_TIMESTAMP(3), "
- "role_code = 'USER' "
- "WHERE role_code = 'AUDITOR'"
- )
- )
- op.drop_constraint(ROLE_CONSTRAINT, "sys_user", type_="check")
- op.create_check_constraint(
- ROLE_CONSTRAINT,
- "sys_user",
- "role_code IN ('USER', 'ADMIN')",
- )
- def downgrade() -> None:
- bind = op.get_bind()
- extracted_count = bind.scalar(
- sa.text(
- "SELECT COUNT(*) FROM doc_document WHERE "
- "content_text IS NOT NULL OR content_extracted_at IS NOT NULL OR "
- "content_extract_status <> 'UNSUPPORTED'"
- )
- )
- oversized_search_count = bind.scalar(
- sa.text(
- "SELECT COUNT(*) FROM doc_document "
- "WHERE OCTET_LENGTH(search_text) > 65535"
- )
- )
- if extracted_count or oversized_search_count:
- raise RuntimeError(
- "0003已包含正文提取结果或超出TEXT容量的检索文本,拒绝有损降级;"
- "请先备份并按数据治理流程处理"
- )
- op.drop_constraint(ROLE_CONSTRAINT, "sys_user", type_="check")
- op.create_check_constraint(
- ROLE_CONSTRAINT,
- "sys_user",
- "role_code IN ('USER', 'ADMIN', 'AUDITOR')",
- )
- # 已失效的历史AUDITOR不会被自动复活或重新授权。
- op.drop_constraint(
- CONTENT_STATUS_CONSTRAINT,
- "doc_document",
- type_="check",
- )
- op.alter_column(
- "doc_document",
- "search_text",
- existing_type=mysql.LONGTEXT(),
- type_=sa.Text(),
- existing_nullable=True,
- )
- op.drop_column("doc_document", "content_extracted_at")
- op.drop_column("doc_document", "content_extract_status")
- op.drop_column("doc_document", "content_text")
|