nex_docus/backend/app/core/migrations.py

101 lines
3.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

"""
轻量级数据库结构迁移
项目没有引入 Alembic新增列通过幂等的 ALTER TABLE 完成:
先查询 information_schema 判断列是否已存在,不存在才执行 ALTER
保证多次启动/执行也不会报错或重复加列。
"""
import logging
from sqlalchemy import text
logger = logging.getLogger(__name__)
# (表名, 列名, 建列语句)
CHAT_MESSAGE_COLUMNS = [
(
"chat_message",
"status",
"ALTER TABLE chat_message ADD COLUMN status VARCHAR(32) NOT NULL DEFAULT 'pending' "
"COMMENT '消息状态: pending/completed/interrupted/error'",
),
(
"chat_message",
"duration_ms",
"ALTER TABLE chat_message ADD COLUMN duration_ms INT DEFAULT NULL "
"COMMENT '生成耗时(毫秒)'",
),
(
"chat_message",
"thinking_log",
"ALTER TABLE chat_message ADD COLUMN thinking_log TEXT DEFAULT NULL "
"COMMENT '思考过程(JSON数组)'",
),
]
async def _column_exists(conn, table_name: str, column_name: str) -> bool:
result = await conn.execute(
text(
"SELECT COUNT(*) FROM information_schema.COLUMNS "
"WHERE TABLE_SCHEMA = DATABASE() "
"AND TABLE_NAME = :table_name AND COLUMN_NAME = :column_name"
),
{"table_name": table_name, "column_name": column_name},
)
return bool(result.scalar())
async def migrate_schema() -> None:
"""为存量数据库补齐新增列,并为历史助手消息回填状态。"""
from app.core.database import engine
added = []
async with engine.begin() as conn:
for table_name, column_name, ddl in CHAT_MESSAGE_COLUMNS:
try:
exists = await _column_exists(conn, table_name, column_name)
except Exception as exc: # noqa: BLE001
logger.warning("检查列 %s.%s 失败,跳过迁移: %s", table_name, column_name, exc)
return
if not exists:
await conn.execute(text(ddl))
added.append(f"{table_name}.{column_name}")
# 历史消息回填:旧的“中断”标记仅存在于 content 末尾,迁移后统一迁移到 status。
# 之后不再用内容比对判断中断status 字段作为唯一依据。
await conn.execute(
text(
"UPDATE chat_message SET status = 'interrupted' "
"WHERE role = 'assistant' "
"AND (status IS NULL OR status = '' OR status = 'pending') "
"AND (content = 'interrupt' OR content LIKE '%\\n\\ninterrupt')"
)
)
# 清理历史“中断”标记文本:迁移后 status 是唯一依据,内容不再混入标记
await conn.execute(
text(
"UPDATE chat_message SET content = '' "
"WHERE status = 'interrupted' AND content = 'interrupt'"
)
)
await conn.execute(
text(
"UPDATE chat_message SET content = TRIM("
"LEFT(content, CHAR_LENGTH(content) - CHAR_LENGTH('\\n\\ninterrupt'))) "
"WHERE status = 'interrupted' AND content LIKE '%\\n\\ninterrupt'"
)
)
await conn.execute(
text(
"UPDATE chat_message SET status = 'completed' "
"WHERE (status IS NULL OR status = '' OR status = 'pending')"
)
)
if added:
logger.info("数据库迁移完成,新增列: %s", ", ".join(added))
else:
logger.info("数据库结构已是最新,无需迁移")