fix(sync): close SQLite handles before restore
Use contextlib.closing for dump, comparison, and restore connections so Windows can unlink the live database immediately after the comparison step.
This commit is contained in:
+8
-4
@@ -18,6 +18,7 @@ bin/db-compare.sh could never work there.
|
|||||||
A fresh machine clones first (git clone <repo> nexus-core), then runs this.
|
A fresh machine clones first (git clone <repo> nexus-core), then runs this.
|
||||||
"""
|
"""
|
||||||
import argparse
|
import argparse
|
||||||
|
import contextlib
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import shutil
|
import shutil
|
||||||
@@ -162,7 +163,7 @@ def dump_db() -> bool:
|
|||||||
if not DB.exists():
|
if not DB.exists():
|
||||||
return False
|
return False
|
||||||
try:
|
try:
|
||||||
with sqlite3.connect(f"file:{DB}?mode=ro", uri=True) as conn:
|
with contextlib.closing(sqlite3.connect(f"file:{DB}?mode=ro", uri=True)) as conn:
|
||||||
ext = _vec0_extension()
|
ext = _vec0_extension()
|
||||||
if ext:
|
if ext:
|
||||||
try:
|
try:
|
||||||
@@ -232,9 +233,12 @@ def compare(db: Path = DB, dump: Path = DB_SQL) -> str:
|
|||||||
if not db.exists():
|
if not db.exists():
|
||||||
return "no-live"
|
return "no-live"
|
||||||
try:
|
try:
|
||||||
with sqlite3.connect(f"file:{db}?mode=ro", uri=True) as live_conn:
|
# closing(), not sqlite3's context manager: that one commits without
|
||||||
|
# closing, and restore_db() unlinks the DB right after calling this -
|
||||||
|
# Windows fails that unlink while any handle is still open.
|
||||||
|
with contextlib.closing(sqlite3.connect(f"file:{db}?mode=ro", uri=True)) as live_conn:
|
||||||
live = _state(live_conn)
|
live = _state(live_conn)
|
||||||
with sqlite3.connect(":memory:") as dump_conn:
|
with contextlib.closing(sqlite3.connect(":memory:")) as dump_conn:
|
||||||
dump_conn.executescript(dump.read_text(encoding="utf-8"))
|
dump_conn.executescript(dump.read_text(encoding="utf-8"))
|
||||||
backup = _state(dump_conn)
|
backup = _state(dump_conn)
|
||||||
except (sqlite3.Error, OSError):
|
except (sqlite3.Error, OSError):
|
||||||
@@ -287,7 +291,7 @@ def restore_db() -> None:
|
|||||||
for suffix in ("", "-wal", "-shm"):
|
for suffix in ("", "-wal", "-shm"):
|
||||||
Path(str(DB) + suffix).unlink(missing_ok=True)
|
Path(str(DB) + suffix).unlink(missing_ok=True)
|
||||||
try:
|
try:
|
||||||
with sqlite3.connect(DB) as conn:
|
with contextlib.closing(sqlite3.connect(DB)) as conn, conn:
|
||||||
conn.executescript(DB_SQL.read_text(encoding="utf-8"))
|
conn.executescript(DB_SQL.read_text(encoding="utf-8"))
|
||||||
print("Memory DB restored (conversations + history + facts).")
|
print("Memory DB restored (conversations + history + facts).")
|
||||||
except sqlite3.Error as exc:
|
except sqlite3.Error as exc:
|
||||||
|
|||||||
+5
-1
@@ -264,13 +264,17 @@ def test_sync_compare_detects_direction(tmp_path):
|
|||||||
"""The guard that stops a stale box from overwriting the other's chats.
|
"""The guard that stops a stale box from overwriting the other's chats.
|
||||||
Both backup and restore refuse to run when this says the wrong thing, so a
|
Both backup and restore refuse to run when this says the wrong thing, so a
|
||||||
silent break here loses conversation history."""
|
silent break here loses conversation history."""
|
||||||
|
import contextlib
|
||||||
import sqlite3 as sq
|
import sqlite3 as sq
|
||||||
sync = _load_sync()
|
sync = _load_sync()
|
||||||
db, dump = tmp_path / "memory.db", tmp_path / "memory.db.sql"
|
db, dump = tmp_path / "memory.db", tmp_path / "memory.db.sql"
|
||||||
|
|
||||||
def write(rows):
|
def write(rows):
|
||||||
db.unlink(missing_ok=True)
|
db.unlink(missing_ok=True)
|
||||||
with sq.connect(db) as conn:
|
# closing() then the connection itself: sqlite3's own context manager
|
||||||
|
# commits but never closes, and Windows refuses to unlink a file that
|
||||||
|
# still has an open handle.
|
||||||
|
with contextlib.closing(sq.connect(db)) as conn, conn:
|
||||||
# updated_at REAL, matching the production schema in store.py. A TEXT
|
# updated_at REAL, matching the production schema in store.py. A TEXT
|
||||||
# column here hid a real TypeError for months: the comparison in
|
# column here hid a real TypeError for months: the comparison in
|
||||||
# _extra() ran str-vs-str in the test and str-vs-float in the field.
|
# _extra() ran str-vs-str in the test and str-vs-float in the field.
|
||||||
|
|||||||
Reference in New Issue
Block a user