#!/usr/bin/env python3 """Local SQLite database layer for the intrusion-detection system. Owns the shared connection factory (WAL mode, busy timeout, single mutable connection reused across the background-scan thread and web readers) and applies schema migrations on open. Query helpers live in :mod:`repositories`. """ from __future__ import annotations import os import sqlite3 import threading from ..utils.constants import DATA_DB_PATH from .schema import apply_migrations _connection: sqlite3.Connection | None = None _lock = threading.RLock() def connect(path: str | None = None) -> sqlite3.Connection: """Open an initialized SQLite connection at ``path``. Enables WAL for concurrent readers during the single-writer background scan and sets a busy timeout so short write contention does not raise. Foreign keys are on and migrations are applied. """ db_path = path or DATA_DB_PATH os.makedirs(os.path.dirname(os.path.abspath(db_path)) or ".", exist_ok=True) conn = sqlite3.connect( db_path, check_same_thread=False, # background scan thread + web handlers isolation_level=None, # autocommit; use explicit BEGIN for batches ) conn.execute("PRAGMA journal_mode=WAL") conn.execute("PRAGMA busy_timeout=5000") conn.execute("PRAGMA synchronous=NORMAL") conn.row_factory = sqlite3.Row apply_migrations(conn) return conn def get_connection() -> sqlite3.Connection: """Return the shared, lazily-created connection (thread-safe).""" global _connection with _lock: if _connection is None: _connection = connect() return _connection def close() -> None: """Close the shared connection, if any.""" global _connection with _lock: if _connection is not None: try: _connection.close() except sqlite3.Error: pass _connection = None