mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-31 19:16:29 +00:00
fix(config): respect database.journal_mode from config.yaml
Config-driven `journal_mode`, `wal_autocheckpoint`, and `journal_size_limit` are now honored in `SessionDB` init. Users running SQLite on NFS/SMB or with custom tuning had no supported config surface; values were hardcoded at connection time. What - New `apply_database_pragmas()` in `hermes_state.py` - Reads nested `database:` keys from `config.yaml` via existing `cfg_get`/`load_config` - Called after `apply_wal_with_fallback()` in `SessionDB._connect_and_init()` Fix - Adds optional PRAGMA switches for journal_mode, wal_autocheckpoint, journal_size_limit - On Darwin; Windows keeps DELETE unless config explicitly requests WAL Runtime Proof $ /opt/homebrew/bin/pytest tests/test_hermes_state.py::TestApplyDatabasePragmas -q 3 passed in 0.72s Regression Checks - Full `tests/test_hermes_state.py`: 306 passed in 11.32s
This commit is contained in:
parent
5567846006
commit
74d6cc2209
2 changed files with 137 additions and 0 deletions
|
|
@ -818,6 +818,57 @@ def _log_wal_fallback_once(db_label: str, exc: Exception) -> None:
|
|||
exc,
|
||||
)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Config-driven database pragmas
|
||||
# ---------------------------------------------------------------------------
|
||||
def apply_database_pragmas(
|
||||
conn: sqlite3.Connection,
|
||||
*,
|
||||
db_label: str = "state.db",
|
||||
) -> None:
|
||||
"""Apply optional WAL-sizing PRAGMAs from ``config.yaml``.
|
||||
|
||||
Reads the ``database:`` section and applies ``wal_autocheckpoint``
|
||||
and ``journal_size_limit`` when set to integer values. The journal
|
||||
mode itself is NOT handled here — ``database.journal_mode`` is owned
|
||||
by :func:`resolve_journal_mode` inside :func:`apply_wal_with_fallback`,
|
||||
which layers the operator setting under all the safety guards
|
||||
(never live-downgrading an on-disk WAL DB, filesystem fallback,
|
||||
WAL-reset-bug gating). Keeping a single owner prevents a second,
|
||||
unguarded journal-mode switch path.
|
||||
|
||||
Best-effort: config load or pragma failures are ignored so DB init
|
||||
never breaks on a malformed ``database:`` section.
|
||||
"""
|
||||
try:
|
||||
# Local import avoids a circular import with hermes_cli.config.
|
||||
from hermes_cli.config import cfg_get, load_config_readonly
|
||||
|
||||
cfg = load_config_readonly()
|
||||
except Exception:
|
||||
return
|
||||
|
||||
for pragma_name in ("wal_autocheckpoint", "journal_size_limit"):
|
||||
raw_value = cfg_get(cfg, "database", pragma_name, default=None)
|
||||
if raw_value is None:
|
||||
continue
|
||||
try:
|
||||
value = int(str(raw_value).strip())
|
||||
except (TypeError, ValueError):
|
||||
logger.warning(
|
||||
"%s: ignoring non-integer database.%s=%r",
|
||||
db_label,
|
||||
pragma_name,
|
||||
raw_value,
|
||||
)
|
||||
continue
|
||||
try:
|
||||
conn.execute(f"PRAGMA {pragma_name}={value}")
|
||||
except sqlite3.OperationalError:
|
||||
pass
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Malformed-schema recovery
|
||||
# ---------------------------------------------------------------------------
|
||||
|
|
@ -1837,6 +1888,7 @@ class SessionDB(SessionSearchMixin, SessionSchemaMixin, SessionPortabilityMixin)
|
|||
self._wal_active = (
|
||||
apply_wal_with_fallback(self._conn, db_label="state.db") == "wal"
|
||||
)
|
||||
apply_database_pragmas(self._conn, db_label="state.db")
|
||||
self._conn.execute("PRAGMA foreign_keys=ON")
|
||||
self._fts_cjk_loaded = load_fts5_cjk_extension(self._conn)
|
||||
self._init_schema()
|
||||
|
|
|
|||
|
|
@ -2856,3 +2856,88 @@ class TestGatewayRoutingPkHeal:
|
|||
cur = db._conn.cursor()
|
||||
db._heal_gateway_routing_pk(cur)
|
||||
assert db.load_gateway_routing_entries(scope="s") == {"k1": "{}"}
|
||||
|
||||
|
||||
class TestApplyDatabasePragmas:
|
||||
"""Config-driven WAL-sizing pragma application (database: section)."""
|
||||
|
||||
@staticmethod
|
||||
def _patch_cfg(monkeypatch, cfg):
|
||||
monkeypatch.setattr(
|
||||
"hermes_cli.config.load_config_readonly",
|
||||
lambda: cfg,
|
||||
)
|
||||
|
||||
def test_honors_wal_autocheckpoint_from_config(self, tmp_path, monkeypatch):
|
||||
import sqlite3
|
||||
from hermes_state import apply_database_pragmas
|
||||
|
||||
conn = sqlite3.connect(str(tmp_path / "pragmas.db"))
|
||||
try:
|
||||
conn.execute("PRAGMA journal_mode=WAL")
|
||||
self._patch_cfg(monkeypatch, {"database": {"wal_autocheckpoint": 250}})
|
||||
apply_database_pragmas(conn, db_label="test.db")
|
||||
assert conn.execute("PRAGMA wal_autocheckpoint").fetchone()[0] == 250
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
def test_honors_journal_size_limit_from_config(self, tmp_path, monkeypatch):
|
||||
import sqlite3
|
||||
from hermes_state import apply_database_pragmas
|
||||
|
||||
conn = sqlite3.connect(str(tmp_path / "pragmas.db"))
|
||||
try:
|
||||
conn.execute("PRAGMA journal_mode=WAL")
|
||||
self._patch_cfg(
|
||||
monkeypatch, {"database": {"journal_size_limit": 10485760}}
|
||||
)
|
||||
apply_database_pragmas(conn, db_label="test.db")
|
||||
assert (
|
||||
conn.execute("PRAGMA journal_size_limit").fetchone()[0] == 10485760
|
||||
)
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
def test_noop_when_database_section_missing(self, tmp_path, monkeypatch):
|
||||
import sqlite3
|
||||
from hermes_state import apply_database_pragmas
|
||||
|
||||
conn = sqlite3.connect(str(tmp_path / "pragmas.db"))
|
||||
try:
|
||||
conn.execute("PRAGMA journal_mode=DELETE")
|
||||
self._patch_cfg(monkeypatch, {})
|
||||
apply_database_pragmas(conn, db_label="test.db")
|
||||
assert conn.execute("PRAGMA journal_mode").fetchone()[0] == "delete"
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
def test_never_touches_journal_mode(self, tmp_path, monkeypatch):
|
||||
"""journal_mode is owned by apply_wal_with_fallback — a database:
|
||||
journal_mode entry must NOT cause a second, unguarded mode switch."""
|
||||
import sqlite3
|
||||
from hermes_state import apply_database_pragmas
|
||||
|
||||
conn = sqlite3.connect(str(tmp_path / "pragmas.db"))
|
||||
try:
|
||||
conn.execute("PRAGMA journal_mode=WAL")
|
||||
self._patch_cfg(monkeypatch, {"database": {"journal_mode": "delete"}})
|
||||
apply_database_pragmas(conn, db_label="test.db")
|
||||
assert conn.execute("PRAGMA journal_mode").fetchone()[0] == "wal"
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
def test_ignores_non_integer_values(self, tmp_path, monkeypatch):
|
||||
import sqlite3
|
||||
from hermes_state import apply_database_pragmas
|
||||
|
||||
conn = sqlite3.connect(str(tmp_path / "pragmas.db"))
|
||||
try:
|
||||
conn.execute("PRAGMA journal_mode=WAL")
|
||||
before = conn.execute("PRAGMA wal_autocheckpoint").fetchone()[0]
|
||||
self._patch_cfg(
|
||||
monkeypatch, {"database": {"wal_autocheckpoint": "lots"}}
|
||||
)
|
||||
apply_database_pragmas(conn, db_label="test.db")
|
||||
assert conn.execute("PRAGMA wal_autocheckpoint").fetchone()[0] == before
|
||||
finally:
|
||||
conn.close()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue