56 lines
1.9 KiB
Python
56 lines
1.9 KiB
Python
|
|
"""Thin sqlite3 wrapper: one connection per process, guarded by a lock.
|
||
|
|
|
||
|
|
Why sqlite3 instead of furtka-core's flat-JSON-plus-flock convention: that
|
||
|
|
convention works because each file is effectively single-writer. This
|
||
|
|
gateway's defining requirement is many unrelated accounts/boxes registering
|
||
|
|
and publishing routes concurrently — a locked JSON file would serialize
|
||
|
|
every tenant's writes against every other tenant's. sqlite3 (stdlib, zero
|
||
|
|
extra dependencies) gives real concurrent relational access instead.
|
||
|
|
"""
|
||
|
|
|
||
|
|
from __future__ import annotations
|
||
|
|
|
||
|
|
import sqlite3
|
||
|
|
import threading
|
||
|
|
from pathlib import Path
|
||
|
|
|
||
|
|
from control_plane import paths
|
||
|
|
|
||
|
|
_SCHEMA_PATH = Path(__file__).parent / "schema.sql"
|
||
|
|
|
||
|
|
|
||
|
|
class Database:
|
||
|
|
def __init__(self, db_path: Path | None = None) -> None:
|
||
|
|
path = db_path or paths.db_path()
|
||
|
|
path.parent.mkdir(parents=True, exist_ok=True)
|
||
|
|
self._lock = threading.Lock()
|
||
|
|
self._conn = sqlite3.connect(path, check_same_thread=False)
|
||
|
|
self._conn.row_factory = sqlite3.Row
|
||
|
|
self._conn.execute("PRAGMA foreign_keys = ON")
|
||
|
|
with self._lock:
|
||
|
|
self._conn.executescript(_SCHEMA_PATH.read_text())
|
||
|
|
self._conn.commit()
|
||
|
|
|
||
|
|
def execute(self, sql: str, params: tuple = ()) -> None:
|
||
|
|
with self._lock:
|
||
|
|
self._conn.execute(sql, params)
|
||
|
|
self._conn.commit()
|
||
|
|
|
||
|
|
def query_one(self, sql: str, params: tuple = ()) -> sqlite3.Row | None:
|
||
|
|
with self._lock:
|
||
|
|
return self._conn.execute(sql, params).fetchone()
|
||
|
|
|
||
|
|
def query_all(self, sql: str, params: tuple = ()) -> list[sqlite3.Row]:
|
||
|
|
with self._lock:
|
||
|
|
return self._conn.execute(sql, params).fetchall()
|
||
|
|
|
||
|
|
def close(self) -> None:
|
||
|
|
with self._lock:
|
||
|
|
self._conn.close()
|
||
|
|
|
||
|
|
|
||
|
|
def row_to_dict(row: sqlite3.Row | None) -> dict | None:
|
||
|
|
if row is None:
|
||
|
|
return None
|
||
|
|
return dict(zip(row.keys(), row, strict=True))
|