feat: add schema drift handling and rebuild
This commit is contained in:
@@ -4,6 +4,7 @@ from __future__ import annotations
|
||||
import os
|
||||
import sqlite3
|
||||
import threading
|
||||
import logging
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
@@ -42,6 +43,21 @@ def get_connection(db_path: str | None = None) -> sqlite3.Connection:
|
||||
_current_path = db_path
|
||||
return _conn
|
||||
|
||||
def _has_schema_version(conn: sqlite3.Connection) -> bool:
|
||||
try:
|
||||
conn.execute("SELECT 1 FROM schema_version").fetchone()
|
||||
return True
|
||||
except sqlite3.OperationalError:
|
||||
return False
|
||||
|
||||
def _has_settings_value_column(conn: sqlite3.Connection) -> bool:
|
||||
try:
|
||||
info = conn.execute("PRAGMA table_info(settings)").fetchall()
|
||||
columns = {row[1]: row[2] for row in info}
|
||||
return 'value' in columns
|
||||
except sqlite3.OperationalError:
|
||||
return False
|
||||
|
||||
|
||||
def init_db(db_path: str | None = None) -> sqlite3.Connection:
|
||||
conn = get_connection(db_path)
|
||||
@@ -53,6 +69,15 @@ def init_db(db_path: str | None = None) -> sqlite3.Connection:
|
||||
with _lock:
|
||||
conn.executescript(schema)
|
||||
conn.commit()
|
||||
if not _has_schema_version(conn):
|
||||
if not _has_settings_value_column(conn):
|
||||
logger = logging.getLogger("slab.db")
|
||||
logger.info("Detected pre-M3 DB, rebuilding schema")
|
||||
conn.executescript("DROP TABLE IF EXISTS settings")
|
||||
conn.executescript(schema)
|
||||
conn.commit()
|
||||
conn.execute("INSERT OR REPLACE INTO schema_version (version) VALUES (1)")
|
||||
|
||||
return conn
|
||||
|
||||
|
||||
|
||||
@@ -111,6 +111,8 @@ CREATE TABLE IF NOT EXISTS settings (
|
||||
value_enc TEXT
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS schema_version (version INTEGER PRIMARY KEY);
|
||||
|
||||
-- 7. prompts + prompt_revisions (append-only revisions)
|
||||
CREATE TABLE IF NOT EXISTS prompts (
|
||||
id INTEGER PRIMARY KEY,
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
import os
|
||||
import tempfile
|
||||
import sqlite3
|
||||
|
||||
import backend.db as db_mod
|
||||
|
||||
|
||||
|
||||
def _setup_temp_db_with_m2_schema(tmp_dir: str) -> str:
|
||||
db_path = os.path.join(tmp_dir, "test.db")
|
||||
conn = sqlite3.connect(db_path)
|
||||
# create old settings table
|
||||
conn.execute("CREATE TABLE settings (key TEXT PRIMARY KEY, value TEXT, value_enc TEXT)")
|
||||
conn.commit()
|
||||
conn.close()
|
||||
return db_path
|
||||
|
||||
|
||||
def test_drift_detection_and_rebuild(monkeypatch):
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
db_path = _setup_temp_db_with_m2_schema(tmp)
|
||||
monkeypatch.setenv("SLAB_DB_PATH", db_path)
|
||||
# init_db should rebuild
|
||||
conn = db_mod.init_db()
|
||||
# check schema_version exists
|
||||
cur = conn.execute("SELECT version FROM schema_version").fetchone()
|
||||
assert cur is not None
|
||||
assert cur[0] == 1
|
||||
# check settings has columns value and value_enc
|
||||
info = conn.execute("PRAGMA table_info(settings)").fetchall()
|
||||
cols = {row[1] for row in info}
|
||||
assert "value" in cols
|
||||
assert "value_enc" in cols
|
||||
# also ensure old settings table dropped and recreated
|
||||
# no other data
|
||||
rows = conn.execute("SELECT key FROM settings").fetchall()
|
||||
assert rows == []
|
||||
Reference in New Issue
Block a user