Commit Graph

4 Commits

Author SHA1 Message Date
iomgaa 0721cf60aa fix: reject an empty column set in insert_sql
`insert_sql(backend, [])` 此前返回 `INSERT OR IGNORE INTO llm_calls () VALUES ()`
与 `INSERT INTO llm_calls () VALUES () ON CONFLICT DO NOTHING`,两条都语法非法。
入参正来自数据库列探测(遇到一张与本库毫无共同列的同名表,裁剪结果就是空),
把"非空"押在调用方的不变量上不成立——共享构造器自己拒,与它既有的"未知
backend""非 COLUMNS 子集"两道校验同款。

连带风险已实测确认: 两个 recorder 的空集回落都发生在调用 `insert_sql` **之前**,
故新增的 raise 不会逃出 SQLite 的 `__init__`(遥测初始化失败必须静默降级)或
PG 的准备期(`_prepare_schema` 里那次调用在 try 之外,异常会一路冒给业务调用方)。
新增 SQLite 空探测结果用例: 构造成功、写入照常、只有 warning。

同时补 PG 侧"探测结果与 COLUMNS 无交集"的回落用例(此前只有 SQLite 侧有),
并把承认缺口的那段测试注释改成断言拒绝。
2026-08-19 13:18:21 -04:00
iomgaa 21a19ab374 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。
2026-08-19 12:12:15 -04:00
iomgaa ecc22b34fc fix: drop the conflict target so partitioned tables can accept writes
PG requires a partitioned table's unique constraints to include the
partition key, so issue #12's RANGE partitioning on created_at forces
the primary key to (call_id, created_at). The old
`ON CONFLICT (call_id) DO NOTHING` then matches no constraint and PG
rejects every row with

    there is no unique or exclusion constraint matching the
    ON CONFLICT specification

which the recorder swallows as a per-row warning: telemetry would go
silently dark under a partitioned deployment. The target-free form is
valid on both table shapes and is literally equivalent on a plain table
(the primary key is its only unique constraint). SQLite's
`INSERT OR IGNORE` already carries no target and is untouched.

Integration coverage on the real PG instance, both inside self-created
temp schemas: a plain table still keeps one row per call_id, and a
table partitioned by created_at now accepts writes and reads them back.
The second case was red before this change with the error above.
2026-08-19 11:34:13 -04:00
iomgaa 1471e0a2c6 refactor: make the telemetry schema a single source of truth
DDL, column order and backfill statements lived twice, once in each
recorder. A public telemetry_schema_sql() would have made three copies,
and the drift shows up downstream as "I ran the printed SQL and the
library still reports a missing column".

Move both DDLs, both backfill lists and the 24 INSERT fields into
telemetry/schema.py verbatim; the recorders now import them and build
_INSERT through insert_sql(backend, COLUMNS) at import time. The
generated statements are byte-identical to the previous constants, so
runtime behaviour is unchanged (the postgres conflict target stays
bound to call_id for now).

insert_sql() validates its columns against COLUMNS: from the next task
on those names come from database probing, not from a constant, so the
subset check is the gate on the only injection surface. The new
telemetry_schema_sql() prints a paste-ready migration script; its
postgres backfill deliberately uses ADD COLUMN IF NOT EXISTS while the
library's own statements do not, because that form takes an ACCESS
EXCLUSIVE lock even when the column exists. Both variants are derived
from one declaration list so their column sets cannot drift.
2026-08-19 11:18:06 -04:00