mirror of
https://github.com/hansjone/oclaw.git
synced 2026-10-19 03:03:27 +08:00
fix(db): prune rows referencing deleted chat_session on init
Remove orphan chat_message, tool_log, oclaw_*, and attachment_acl rows when session_id no longer exists (e.g. legacy deletes without FK). Add regression tests. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
289cac4dd6
commit
7ea0d59512
2 changed files with 82 additions and 0 deletions
|
|
@ -997,6 +997,21 @@ class SqliteStore:
|
||||||
conn.execute(
|
conn.execute(
|
||||||
"CREATE INDEX IF NOT EXISTS idx_chat_message_session_id_id ON chat_message(session_id, id)"
|
"CREATE INDEX IF NOT EXISTS idx_chat_message_session_id_id ON chat_message(session_id, id)"
|
||||||
)
|
)
|
||||||
|
# 若历史上曾在未开启 foreign_keys 的连接里删除过 ``chat_session``,或表创建时未带外键,
|
||||||
|
# 会留下指向已不存在会话的行。每次初始化库时做一次幂等清理。
|
||||||
|
self._prune_rows_for_missing_chat_session(conn)
|
||||||
|
|
||||||
|
def _prune_rows_for_missing_chat_session(self, conn: sqlite3.Connection) -> None:
|
||||||
|
sid_alive = "(SELECT id FROM chat_session)"
|
||||||
|
conn.execute(f"DELETE FROM chat_message WHERE session_id NOT IN {sid_alive}")
|
||||||
|
conn.execute(f"DELETE FROM tool_log WHERE session_id NOT IN {sid_alive}")
|
||||||
|
conn.execute(
|
||||||
|
"DELETE FROM oclaw_attempt WHERE run_id NOT IN (SELECT run_id FROM oclaw_run) "
|
||||||
|
f"OR run_id IN (SELECT run_id FROM oclaw_run WHERE session_id NOT IN {sid_alive})"
|
||||||
|
)
|
||||||
|
conn.execute(f"DELETE FROM oclaw_run WHERE session_id NOT IN {sid_alive}")
|
||||||
|
conn.execute(f"DELETE FROM oclaw_task WHERE session_id NOT IN {sid_alive}")
|
||||||
|
conn.execute(f"DELETE FROM attachment_acl WHERE session_id NOT IN {sid_alive}")
|
||||||
|
|
||||||
def _seed_builtin_llm_profiles(self, conn: sqlite3.Connection) -> None:
|
def _seed_builtin_llm_profiles(self, conn: sqlite3.Connection) -> None:
|
||||||
ts = utc_now_iso()
|
ts = utc_now_iso()
|
||||||
|
|
|
||||||
67
tests/test_session_orphan_prune.py
Normal file
67
tests/test_session_orphan_prune.py
Normal file
|
|
@ -0,0 +1,67 @@
|
||||||
|
"""_init_db prunes rows that reference deleted chat_session (legacy FK-off deletes)."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import sqlite3
|
||||||
|
import tempfile
|
||||||
|
import unittest
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from oclaw.platform.persistence.sqlite_store import SqliteStore
|
||||||
|
|
||||||
|
|
||||||
|
class SessionOrphanPruneTests(unittest.TestCase):
|
||||||
|
def test_chat_message_pruned_after_session_deleted_with_fk_off(self) -> None:
|
||||||
|
with tempfile.TemporaryDirectory(ignore_cleanup_errors=True) as td:
|
||||||
|
db = Path(td) / "x.sqlite"
|
||||||
|
s = SqliteStore(str(db))
|
||||||
|
sess = s.create_session("t")
|
||||||
|
s.add_message(sess.id, "user", "hello")
|
||||||
|
raw = sqlite3.connect(str(db))
|
||||||
|
raw.execute("PRAGMA foreign_keys=OFF")
|
||||||
|
raw.execute("DELETE FROM chat_session WHERE id = ?", (sess.id,))
|
||||||
|
raw.commit()
|
||||||
|
raw.close()
|
||||||
|
self.assertGreater(
|
||||||
|
int(
|
||||||
|
sqlite3.connect(str(db))
|
||||||
|
.execute("select count(*) from chat_message where session_id = ?", (sess.id,))
|
||||||
|
.fetchone()[0]
|
||||||
|
),
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
SqliteStore(str(db))
|
||||||
|
n = int(
|
||||||
|
sqlite3.connect(str(db))
|
||||||
|
.execute("select count(*) from chat_message where session_id = ?", (sess.id,))
|
||||||
|
.fetchone()[0]
|
||||||
|
)
|
||||||
|
self.assertEqual(n, 0)
|
||||||
|
|
||||||
|
def test_oclaw_task_pruned_when_session_row_missing(self) -> None:
|
||||||
|
with tempfile.TemporaryDirectory(ignore_cleanup_errors=True) as td:
|
||||||
|
db = Path(td) / "y.sqlite"
|
||||||
|
store = SqliteStore(str(db))
|
||||||
|
sess = store.create_session("s")
|
||||||
|
with store._connect() as conn:
|
||||||
|
conn.execute(
|
||||||
|
"""
|
||||||
|
INSERT INTO oclaw_task(
|
||||||
|
id, tenant_id, session_id, status, payload, result,
|
||||||
|
attempt_count, last_error, created_at, updated_at
|
||||||
|
) VALUES (?, ?, ?, 'done', '{}', '{}', 0, '', datetime('now'), datetime('now'))
|
||||||
|
""",
|
||||||
|
("task-1", "tenant-1", sess.id),
|
||||||
|
)
|
||||||
|
raw = sqlite3.connect(str(db))
|
||||||
|
raw.execute("PRAGMA foreign_keys=OFF")
|
||||||
|
raw.execute("DELETE FROM chat_session WHERE id = ?", (sess.id,))
|
||||||
|
raw.commit()
|
||||||
|
raw.close()
|
||||||
|
SqliteStore(str(db))
|
||||||
|
c = int(sqlite3.connect(str(db)).execute("select count(*) from oclaw_task").fetchone()[0])
|
||||||
|
self.assertEqual(c, 0)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
Loading…
Add table
Add a link
Reference in a new issue