refactor: converge the missing-column warning into the schema module

两个 recorder 里逐字重复的 `_missing_columns_message` 收敛为 `schema.py` 的
`missing_columns_warning(backend, missing, *, alien_table)`。这条消息拼的是
给人执行的 DDL,与库自己执行的 ALTER 必须同源——留在两个 recorder 里等于在
单一事实源上开了个口子,而 Task 7 的文档还要引用这个消息格式。

纯收敛,行为零变化: 两端语句仍分别取自各自的 `SQLITE_BACKFILL` / `PG_BACKFILL`
(函数内不硬编码任何 DDL 文本),措辞、标点与换行逐字保留。已用改动前后的两份
实现对 16 组入参(4 种缺列组合 × alien 两态 × 两后端)逐串比对,输出完全相同。

顺带把 postgres.py 从 281 行降到 250、sqlite.py 降到 157。
This commit is contained in:
2026-08-19 12:12:15 -04:00
parent e949edb62a
commit 21a19ab374
3 changed files with 79 additions and 81 deletions
+9 -40
View File
@@ -21,7 +21,13 @@ from typing import TYPE_CHECKING
from loguru import logger
from polygateway.telemetry.schema import COLUMNS, PG_BACKFILL, PG_DDL, insert_sql
from polygateway.telemetry.schema import (
COLUMNS,
PG_BACKFILL,
PG_DDL,
insert_sql,
missing_columns_warning,
)
if TYPE_CHECKING:
import asyncpg
@@ -35,44 +41,6 @@ _EXISTING_COLUMNS = (
"WHERE attrelid = to_regclass('llm_calls') AND attnum > 0 AND NOT attisdropped"
)
# 缺列 warning 要打印可直接执行的补列语句,与库内 ALTER 同源(不许两份)
_BACKFILL_STATEMENTS = dict(PG_BACKFILL)
def _missing_columns_message(missing: list[str], *, alien_table: bool) -> str:
"""拼 manual 档的缺列告警: 逐列点名 + 讲清后果 + 给出可直接执行的 SQL。
只说"缺列"是不够的: 静默丢维度的后果是多租户账目全归空串且无任何报错,
看告警的人必须一眼看到丢的是哪几个维度、以及怎么补。
Args:
missing: 缺失的列名(按 `COLUMNS` 保序)。
alien_table: 连主键列 `call_id` 都没有——该表多半不是本库的 `llm_calls`。
Returns:
单条 warning 的完整文本(库只在准备期发一次,不逐行发)。
"""
statements = [
f"{_BACKFILL_STATEMENTS[column]};" for column in missing if column in _BACKFILL_STATEMENTS
]
unknown = [column for column in missing if column not in _BACKFILL_STATEMENTS]
if unknown:
# 这些列本库从未经 ALTER 补过(建表即有),给不出单条 ALTER,指向完整脚本
statements.append(
f"-- 另缺 {', '.join(unknown)};完整建表脚本见 "
'polygateway.telemetry_schema_sql("postgres")'
)
head = (
"Postgres 遥测表 llm_calls 缺主键列 call_id,很可能不是本库的遥测表"
"(库不做二次判定,仍照常尝试写入)"
if alien_table
else "Postgres 遥测表 llm_calls 缺列,且 auto_migrate=False(库不发任何 DDL)"
)
return (
f"{head};以下维度不会被记录: {', '.join(missing)}"
"补列请自行执行(建议挑低峰,ALTER 取 ACCESS EXCLUSIVE 锁):\n" + "\n".join(statements)
)
class PostgresRecorder:
"""TelemetryRecorder 端口的 Postgres 实现;asyncpg 原生异步,无线程桥接。"""
@@ -231,7 +199,8 @@ class PostgresRecorder:
if missing:
# 单参数传入: 补列 SQL 里带 `'{}'::jsonb` 字面量,拼进 format 模板会被当占位符
logger.warning(
"{}", _missing_columns_message(missing, alien_table="call_id" not in existing)
"{}",
missing_columns_warning("postgres", missing, alien_table="call_id" not in existing),
)
return effective