2026-11-01 20:42:53 -05:00
|
|
|
|
"""只按明确的源驱动错误分类,不把含凭据的驱动文本写入响应。"""
|
|
|
|
|
|
|
|
|
|
|
|
from sqlalchemy.exc import TimeoutError as PoolTimeoutError
|
|
|
|
|
|
|
|
|
|
|
|
TRANSIENT_CODES = frozenset({"SOURCE_CONNECTION_LOST", "SOURCE_TIMEOUT"})
|
|
|
|
|
|
MESSAGES = {
|
2026-11-02 22:20:55 -05:00
|
|
|
|
"TASK_AUTHORIZATION_REVOKED": "请求账号已停用或权限已撤销,请重新授权后创建任务",
|
2026-11-01 20:42:53 -05:00
|
|
|
|
"SOURCE_CONNECTION_LOST": "源连接中断,请检查网络和只读连接",
|
|
|
|
|
|
"SOURCE_TIMEOUT": "源连接或查询超时",
|
|
|
|
|
|
"SOURCE_AUTH_ERROR": "源账号认证失败,请核对只读账号配置",
|
|
|
|
|
|
"SOURCE_SCHEMA_ERROR": "源表或字段不匹配,请核对版本与映射",
|
|
|
|
|
|
"SOURCE_CONFIG_ERROR": "源连接配置不完整或任务排队后发生变化",
|
|
|
|
|
|
"SOURCE_DRIVER_ERROR": "源数据库驱动不可用,请检查已验证的ODBC驱动",
|
|
|
|
|
|
"QUERY_FILE_MISSING": "缺少源查询文件,请核对部署包",
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def classify_source_error(exc, fallback="READ_ERROR"):
|
2026-11-02 22:20:55 -05:00
|
|
|
|
from app.core.exceptions import ForbiddenError
|
|
|
|
|
|
|
|
|
|
|
|
if isinstance(exc, ForbiddenError):
|
|
|
|
|
|
return "TASK_AUTHORIZATION_REVOKED"
|
2026-11-01 20:42:53 -05:00
|
|
|
|
if isinstance(exc, (TimeoutError, PoolTimeoutError)):
|
|
|
|
|
|
return "SOURCE_TIMEOUT"
|
|
|
|
|
|
if isinstance(exc, ModuleNotFoundError):
|
|
|
|
|
|
return "SOURCE_DRIVER_ERROR"
|
|
|
|
|
|
if isinstance(exc, FileNotFoundError):
|
|
|
|
|
|
return "QUERY_FILE_MISSING"
|
|
|
|
|
|
original = getattr(exc, "orig", exc)
|
|
|
|
|
|
args = getattr(original, "args", ())
|
|
|
|
|
|
state = str(args[0]).upper() if args else ""
|
|
|
|
|
|
if state in {"HYT00", "HYT01"}:
|
|
|
|
|
|
return "SOURCE_TIMEOUT"
|
|
|
|
|
|
if state in {"08S01", "08001", "08003", "08006"}:
|
|
|
|
|
|
return "SOURCE_CONNECTION_LOST"
|
|
|
|
|
|
if state == "28000":
|
|
|
|
|
|
return "SOURCE_AUTH_ERROR"
|
|
|
|
|
|
if state in {"42S02", "42S22", "07002"}:
|
|
|
|
|
|
return "SOURCE_SCHEMA_ERROR"
|
|
|
|
|
|
return fallback
|