From 5d8403e75622403e8a7b8eb996d8e2975af04999 Mon Sep 17 00:00:00 2001 From: host Date: Wed, 28 Oct 2026 22:49:06 +0800 Subject: [PATCH] fix(db): register auto-memory tables in _OR_REPLACE_PK MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Postgres 翻译层对未登记主键的表把 INSERT OR REPLACE 退化为 ON CONFLICT DO NOTHING,致使 memory_settings、task_memory_flags、 memory_extraction_log 首写之后的所有更新静默丢弃(2026-10-28 生产实测:提取日志永远停在首写原因)。补三张表主键登记,并 在翻译层加回归测试(SQLite 原生 OR REPLACE 测不出来)。 --- cloud/db.py | 7 +++++++ cloud/tests/test_postgres_backend.py | 27 +++++++++++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/cloud/db.py b/cloud/db.py index 9b08d57..0eda9e1 100644 --- a/cloud/db.py +++ b/cloud/db.py @@ -415,6 +415,13 @@ _OR_REPLACE_PK = { "project_members": ("project_id", "user_id"), "slack_users": ("slack_id",), "github_app_config": ("id",), + # 自动记忆三张表:主键登记缺失时 Postgres 翻译退化为 + # ON CONFLICT DO NOTHING,首写之后的所有更新静默丢弃 + # (2026-10-28 生产实测:提取日志永远停在首写原因, + # 改设置同理;SQLite 原生 OR REPLACE 测不出来)。 + "memory_settings": ("user_id",), + "task_memory_flags": ("task_id",), + "memory_extraction_log": ("task_id",), } _IDENTITY_TABLES = frozenset( diff --git a/cloud/tests/test_postgres_backend.py b/cloud/tests/test_postgres_backend.py index 8166ab3..b292b1c 100644 --- a/cloud/tests/test_postgres_backend.py +++ b/cloud/tests/test_postgres_backend.py @@ -198,6 +198,33 @@ class PlaceholderTranslationTestCase(unittest.TestCase): self.assertIn("%s", translated) self.assertNotIn("?", translated) + def test_auto_memory_tables_replace_updates_not_ignores(self): + # 2026-10-28 生产教训:这三张表未登记主键时翻译退化为 + # ON CONFLICT DO NOTHING,首写之后的更新静默丢弃;SQLite + # 原生 OR REPLACE 测不出来,必须在翻译层断言。 + cases = [ + ("INSERT OR REPLACE INTO memory_settings" + "(user_id, generate_memories, updated_at) VALUES(?, ?, ?)", + "ON CONFLICT (user_id) DO UPDATE SET", + "generate_memories = EXCLUDED.generate_memories"), + ("INSERT OR REPLACE INTO task_memory_flags" + "(task_id, use_memories, generate_memories, updated_at)" + " VALUES(?, ?, ?, ?)", + "ON CONFLICT (task_id) DO UPDATE SET", + "use_memories = EXCLUDED.use_memories"), + ("INSERT OR REPLACE INTO memory_extraction_log" + "(task_id, status, reason, processed_at) VALUES(?, ?, ?, ?)", + "ON CONFLICT (task_id) DO UPDATE SET", + "status = EXCLUDED.status"), + ] + for sql, conflict, setter in cases: + translated, kind = _translate_sql(sql) + self.assertEqual(kind, "normal") + self.assertIn(conflict, translated) + self.assertIn(setter, translated) + self.assertNotIn("DO NOTHING", translated) + self.assertNotIn("OR REPLACE", translated) + def test_autoincrement_becomes_identity(self): translated, _ = _translate_sql( "CREATE TABLE t(id INTEGER PRIMARY KEY AUTOINCREMENT, x TEXT)")