feat(db): schema v15 — welcome_template singleton table
This commit is contained in:
parent
96281f884c
commit
33e7107637
2 changed files with 66 additions and 1 deletions
32
src/db.py
32
src/db.py
|
|
@ -39,7 +39,7 @@ def _maybe_instrument(con, db_tag: str):
|
||||||
|
|
||||||
_SAFE_IDENTIFIER = re.compile(r"^[a-zA-Z_][a-zA-Z0-9_]{0,63}$")
|
_SAFE_IDENTIFIER = re.compile(r"^[a-zA-Z_][a-zA-Z0-9_]{0,63}$")
|
||||||
|
|
||||||
SCHEMA_VERSION = 20
|
SCHEMA_VERSION = 21
|
||||||
|
|
||||||
_SYSTEM_SCHEMA = """
|
_SYSTEM_SCHEMA = """
|
||||||
CREATE TABLE IF NOT EXISTS schema_version (
|
CREATE TABLE IF NOT EXISTS schema_version (
|
||||||
|
|
@ -405,6 +405,18 @@ CREATE TABLE IF NOT EXISTS resource_grants (
|
||||||
assigned_by VARCHAR,
|
assigned_by VARCHAR,
|
||||||
UNIQUE (group_id, resource_type, resource_id)
|
UNIQUE (group_id, resource_type, resource_id)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
-- v21: customizable analyst-bootstrap welcome prompt.
|
||||||
|
-- Singleton row (id=1). NULL content means "use the default template
|
||||||
|
-- shipped at config/claude_md_template.txt"; admin-edited override
|
||||||
|
-- stores the raw Jinja2 source string.
|
||||||
|
CREATE TABLE IF NOT EXISTS welcome_template (
|
||||||
|
id INTEGER PRIMARY KEY DEFAULT 1,
|
||||||
|
content TEXT,
|
||||||
|
updated_at TIMESTAMP,
|
||||||
|
updated_by VARCHAR,
|
||||||
|
CONSTRAINT singleton CHECK (id = 1)
|
||||||
|
);
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1614,6 +1626,17 @@ _V3_TO_V4_MIGRATIONS = [
|
||||||
""",
|
""",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
_V20_TO_V21_MIGRATIONS = [
|
||||||
|
"""CREATE TABLE IF NOT EXISTS welcome_template (
|
||||||
|
id INTEGER PRIMARY KEY DEFAULT 1,
|
||||||
|
content TEXT,
|
||||||
|
updated_at TIMESTAMP,
|
||||||
|
updated_by VARCHAR,
|
||||||
|
CONSTRAINT singleton CHECK (id = 1)
|
||||||
|
)""",
|
||||||
|
"INSERT INTO welcome_template (id, content) VALUES (1, NULL) ON CONFLICT (id) DO NOTHING",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
def _ensure_schema(conn: duckdb.DuckDBPyConnection) -> None:
|
def _ensure_schema(conn: duckdb.DuckDBPyConnection) -> None:
|
||||||
"""Create tables if they don't exist. Apply migrations if schema version changed.
|
"""Create tables if they don't exist. Apply migrations if schema version changed.
|
||||||
|
|
@ -1672,6 +1695,10 @@ def _ensure_schema(conn: duckdb.DuckDBPyConnection) -> None:
|
||||||
"INSERT INTO schema_version (version) VALUES (?)",
|
"INSERT INTO schema_version (version) VALUES (?)",
|
||||||
[SCHEMA_VERSION],
|
[SCHEMA_VERSION],
|
||||||
)
|
)
|
||||||
|
conn.execute(
|
||||||
|
"INSERT INTO welcome_template (id, content) VALUES (1, NULL) "
|
||||||
|
"ON CONFLICT (id) DO NOTHING"
|
||||||
|
)
|
||||||
# Fresh-install seed is handled by the unconditional
|
# Fresh-install seed is handled by the unconditional
|
||||||
# _seed_core_roles call at the bottom of _ensure_schema —
|
# _seed_core_roles call at the bottom of _ensure_schema —
|
||||||
# left as a no-op branch here so the migration ladder still
|
# left as a no-op branch here so the migration ladder still
|
||||||
|
|
@ -1749,6 +1776,9 @@ def _ensure_schema(conn: duckdb.DuckDBPyConnection) -> None:
|
||||||
if current < 20:
|
if current < 20:
|
||||||
for sql in _V19_TO_V20_MIGRATIONS:
|
for sql in _V19_TO_V20_MIGRATIONS:
|
||||||
conn.execute(sql)
|
conn.execute(sql)
|
||||||
|
if current < 21:
|
||||||
|
for sql in _V20_TO_V21_MIGRATIONS:
|
||||||
|
conn.execute(sql)
|
||||||
conn.execute(
|
conn.execute(
|
||||||
"UPDATE schema_version SET version = ?, applied_at = current_timestamp",
|
"UPDATE schema_version SET version = ?, applied_at = current_timestamp",
|
||||||
[SCHEMA_VERSION],
|
[SCHEMA_VERSION],
|
||||||
|
|
|
||||||
35
tests/test_welcome_template_migration.py
Normal file
35
tests/test_welcome_template_migration.py
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
"""v20 → v21 migration: adds welcome_template singleton table."""
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import duckdb
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from src.db import SCHEMA_VERSION, _ensure_schema, get_schema_version
|
||||||
|
|
||||||
|
|
||||||
|
def _open(path: Path) -> duckdb.DuckDBPyConnection:
|
||||||
|
return duckdb.connect(str(path))
|
||||||
|
|
||||||
|
|
||||||
|
def test_v21_creates_welcome_template_table(tmp_path):
|
||||||
|
db_path = tmp_path / "system.duckdb"
|
||||||
|
conn = _open(db_path)
|
||||||
|
# Pretend we're on v20: write a v20-shaped DB by running schema then
|
||||||
|
# rolling the version row back.
|
||||||
|
_ensure_schema(conn)
|
||||||
|
conn.execute("UPDATE schema_version SET version = 20")
|
||||||
|
conn.execute("DROP TABLE IF EXISTS welcome_template")
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
# Re-open: migration ladder runs.
|
||||||
|
conn = _open(db_path)
|
||||||
|
_ensure_schema(conn)
|
||||||
|
assert get_schema_version(conn) == SCHEMA_VERSION
|
||||||
|
# Singleton row must exist with NULL content (= use shipped default).
|
||||||
|
rows = conn.execute(
|
||||||
|
"SELECT id, content, updated_at, updated_by FROM welcome_template"
|
||||||
|
).fetchall()
|
||||||
|
assert len(rows) == 1
|
||||||
|
assert rows[0][0] == 1 # singleton id
|
||||||
|
assert rows[0][1] is None # NULL = default
|
||||||
Loading…
Reference in a new issue