fix: transplant PG init backfill rollback scope + audit-loop state upsert registration (code-to10 v2, from 854f810)

- db: translate INSERT OR IGNORE ... SELECT to ON CONFLICT DO NOTHING;
  register task_audit_loop_state (task_id) in _OR_REPLACE_PK (direct port).
- server: _backfill_default_quotas uses SAVEPOINT containment, no longer
  commits the caller's transaction (direct port; callers are init_db only).
- worker: NOT ported — integration _coord_child_rows has no column-fallback
  retry loop, so the InFailedSqlTransaction cascade site does not exist here.
- tests: rewritten for the integration base (no cloud.audit_loop /
  _coord_audit_loop_merge here); real-PG cases skip without DSN/driver.

(cherry picked from commit 79e6843610fcd275af874e7b4afc57af7ffa579e)
This commit is contained in:
host committed 2026-11-03 02:49:26 +00:00
1 parent 741eb387ed
commit 2dc35bd331
3 files changed
+341 -4

No files matched your search

+33 -1
View File
@@ -325,7 +325,9 @@ def _load_postgres_driver():
# (语义弱于 REPLACE,调用点只有 project_members 与
# slack_users 两张已知主键表,不会走到退化分支)。
# - ``INSERT OR IGNORE`` -> ``ON CONFLICT DO NOTHING``:无冲突
# 目标,任何唯一冲突都跳过,不改已有行。
# 目标,任何唯一冲突都跳过,不改已有行;VALUES 形与
# SELECT 形两种写法都翻译(SELECT 形漏翻曾致新鲜 PG 库
# init 失败,见 _OR_IGNORE_SELECT_RE)。
# - ``PRAGMA table_info(<表>)`` -> information_schema 查询,
# 返回 (cid, name, type, notnull, dflt_value, pk) 六元组,
# 与 sqlite 的 PRAGMA table_info 行形状一致,调用方取 row[1]
@@ -423,6 +425,13 @@ _OR_REPLACE_PK = {
"memory_settings": ("user_id",),
"task_memory_flags": ("task_id",),
"memory_extraction_log": ("task_id",),
# 审计循环状态单行表:主键 task_id。未登记时 Postgres 下
# INSERT OR REPLACE 退化为 ON CONFLICT DO NOTHING,已存在
# 行的 round / last_decision 等更新静默丢弃(2026-10-31
# PG 验证实测:跨进程竞态中包 round=2 而状态表 round 恒
# 为 1、轮次耗尽时 last_decision 仍为 pushback;SQLite
# 原生 OR REPLACE 测不出来,与自动记忆三张表同款坑)。
"task_audit_loop_state": ("task_id",),
}
_IDENTITY_TABLES = frozenset(
@@ -437,6 +446,13 @@ _OR_IGNORE_RE = _re.compile(
r"^\s*INSERT\s+OR\s+IGNORE\s+INTO\s+(\w+)\s*\(([^)]+)\)\s*"
r"VALUES\s*\((.*)\)\s*$",
_re.IGNORECASE | _re.DOTALL)
# INSERT OR IGNORE ... SELECT 形(VALUES 形之外的第二种写法,
# 如配额回填):PG 没有 OR IGNORE 语法,不翻译会直接报
# syntax error at or near "OR"(2026-10-31 新鲜 PG 库复现)。
_OR_IGNORE_SELECT_RE = _re.compile(
r"^\s*INSERT\s+OR\s+IGNORE\s+INTO\s+(\w+)\s*"
r"(\([^)]*\))?\s+SELECT\s+(.*)$",
_re.IGNORECASE | _re.DOTALL)
_INSERT_TABLE_RE = _re.compile(
r"^\s*INSERT\s+INTO\s+(?:\w+\.)?(\w+)", _re.IGNORECASE)
_PRAGMA_INFO_RE = _re.compile(
@@ -506,6 +522,22 @@ def _translate_sql(sql):
"INSERT INTO %s (%s) VALUES (%s) ON CONFLICT DO NOTHING"
% (table, cols, values))
return _convert_placeholders(translated), "normal"
m = _OR_IGNORE_SELECT_RE.match(stripped)
if m:
# INSERT OR IGNORE ... SELECT 形 -> INSERT INTO ... SELECT
# ... ON CONFLICT DO NOTHING:与 VALUES 形同款语义(任何
# 唯一冲突都跳过、不改已有行)。旧版只认 VALUES 形,SELECT
# 形原样透传到 PG,直接报 syntax error at or near "OR";
# 配额回填就是 SELECT 形,init_db 里它的 except 又把整个
# 事务 rollback,migrate 的 ALTER 一并被丢掉(见 server
# _backfill_default_quotas 的保存点注释)。
table = m.group(1)
cols = m.group(2) or ""
select_body = m.group(3).rstrip().rstrip(";")
translated = (
"INSERT INTO %s %s SELECT %s ON CONFLICT DO NOTHING"
% (table, cols, select_body))
return _convert_placeholders(translated), "normal"
translated = _AUTOINCREMENT_RE.sub(
"BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY", sql)
translated = _convert_placeholders(translated)