44 lines
1.3 KiB
Python
44 lines
1.3 KiB
Python
"""Shared SQLite primitives for the small per-profile / board stores."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import contextlib
|
|
import sqlite3
|
|
|
|
|
|
def add_column_if_missing(conn: sqlite3.Connection, table: str, column: str, ddl: str) -> bool:
|
|
"""``ALTER TABLE <table> ADD COLUMN <ddl>``, idempotent across races.
|
|
|
|
Returns True when this call added the column; swallows the ``duplicate column name`` error a
|
|
concurrent migrator may have caused. ``column`` is the human-readable name, ``ddl`` the
|
|
actual definition.
|
|
"""
|
|
try:
|
|
conn.execute(f"ALTER TABLE {table} ADD COLUMN {ddl}")
|
|
return True
|
|
except sqlite3.OperationalError as exc:
|
|
if "duplicate column name" in str(exc).lower():
|
|
return False
|
|
raise
|
|
|
|
|
|
@contextlib.contextmanager
|
|
def write_txn(conn: sqlite3.Connection):
|
|
"""An IMMEDIATE write transaction: at most one concurrent writer wins.
|
|
|
|
The explicit ROLLBACK is guarded so a SQLite auto-rollback (no active transaction left under EIO
|
|
/ lock contention / corruption) cannot shadow the original exception with a spurious rollback
|
|
error.
|
|
"""
|
|
conn.execute("BEGIN IMMEDIATE")
|
|
try:
|
|
yield conn
|
|
except Exception:
|
|
try:
|
|
conn.execute("ROLLBACK")
|
|
except sqlite3.OperationalError:
|
|
pass
|
|
raise
|
|
else:
|
|
conn.execute("COMMIT")
|