fix(sessions): restore trigram after deferred bootstrap

This commit is contained in:
fangliquan
2026-09-08 21:50:58 +08:00
committed by kshitij
parent 8b5123e238
commit 5ca028d16a
2 changed files with 35 additions and 1 deletions
+18 -1
View File
@@ -547,10 +547,12 @@ class SessionSchemaMixin:
"""Body of :meth:`_recover_stale_fts`; caller holds rebuild authority. One write
transaction, so no canonical writer slips between rebuild and trigger restoration."""
try:
include_trigram = self._fts_table_probe(cursor, "messages_fts_trigram") is True
trigram_present = self._fts_table_probe(cursor, "messages_fts_trigram") is True
except (sqlite3.DatabaseError, UnicodeDecodeError):
# A corrupt vtable may fail even a LIMIT 0 probe; still include it in the drop-and-recreate.
include_trigram = True
else:
include_trigram = trigram_present or (not legacy and self._trigram_tokenizer_available(cursor))
drop_sql = "".join(f"DROP TRIGGER IF EXISTS {trigger};" for trigger in _FTS_TRIGGERS)
if include_trigram:
@@ -590,6 +592,21 @@ class SessionSchemaMixin:
logger.warning("Rebuilt stale state.db FTS indexes from canonical messages and restored sync triggers.")
return True
def _trigram_tokenizer_available(self, cursor: sqlite3.Cursor) -> bool:
"""Probe trigram support without publishing a persistent FTS object."""
probe = "temp.hermes_fts5_trigram_probe"
cursor.execute(f"DROP TABLE IF EXISTS {probe}")
try:
cursor.execute(f"CREATE VIRTUAL TABLE {probe} USING fts5(content, tokenize='trigram')")
except sqlite3.OperationalError as exc:
if not self._is_trigram_unavailable_error(exc):
raise
self._warn_trigram_unavailable(exc)
return False
finally:
cursor.execute(f"DROP TABLE IF EXISTS {probe}")
return True
# ── Declarative column reconciliation ──────────────────────────────────
@staticmethod
@@ -1,5 +1,6 @@
"""Fresh state.db FTS bootstrap must honor cross-process rebuild admission."""
import sqlite3
import subprocess
import sys
from pathlib import Path
@@ -22,6 +23,17 @@ with fts_rebuild_admission(Path(sys.argv[1]), timeout_seconds=0) as acquired:
"""
def _trigram_supported() -> bool:
conn = sqlite3.connect(":memory:")
try:
conn.execute("CREATE VIRTUAL TABLE trigram_probe USING fts5(content, tokenize='trigram')")
except sqlite3.OperationalError:
return False
finally:
conn.close()
return True
def test_fresh_fts_bootstrap_does_not_publish_schema_without_admission(tmp_path, monkeypatch):
"""A fresh opener that loses admission leaves no partially initialized FTS surface."""
db_path = tmp_path / "state.db"
@@ -53,6 +65,11 @@ def test_fresh_fts_bootstrap_does_not_publish_schema_without_admission(tmp_path,
try:
assert recovered._fts_stale is False
assert recovered._fts_enabled is True
if _trigram_supported():
assert recovered._trigram_available is True
assert recovered._conn.execute(
"SELECT 1 FROM sqlite_master WHERE name = 'messages_fts_trigram'"
).fetchone() is not None
recovered.create_session("s1", source="test")
recovered.append_message("s1", "user", "fresh bootstrap recovered")
assert recovered.search_messages("recovered")