don't crash server when sqlite-zstd extension can't be loaded
Build and Push Docker Container / build-and-push (push) Successful in 3m58s

This commit is contained in:
2026-04-06 17:22:27 +02:00
parent 6c34df103e
commit ed41c32ad8
+35 -5
View File
@@ -1,13 +1,21 @@
from datetime import datetime, timezone
import asyncio, sqlite3, json, os
import asyncio, sqlite3, json, os, logging, sys
from pathlib import Path
logger = logging.getLogger(__name__)
if not logger.handlers:
_handler = logging.StreamHandler(stream=sys.stdout)
_handler.setFormatter(logging.Formatter(fmt="%(levelname)s %(module)s: %(message)s"))
logger.addHandler(_handler)
logger.propagate = False
_ZSTD_EXT = Path(os.environ.get("SQLITE_ZSTD_EXT", "/usr/local/lib/libsqlite_zstd.so")).expanduser().resolve()
class GameplayDatabase:
def __init__(self, db_path:str, busy_timeout_ms:int=5000):
self.db_path = db_path
self.busy_timeout_ms = max(1000, int(busy_timeout_ms))
self._zstd_available = False
self._initialize_database()
def _connect(self) -> sqlite3.Connection:
@@ -16,13 +24,18 @@ class GameplayDatabase:
timeout=max(1, self.busy_timeout_ms // 1000),
isolation_level=None,
)
connection.row_factory = sqlite3.Row
if Path(_ZSTD_EXT).exists():
if _ZSTD_EXT.exists():
try:
connection.enable_load_extension(True)
connection.load_extension(str(_ZSTD_EXT))
self._zstd_available = True
except sqlite3.OperationalError as e:
logger.warning(f"sqlite-zstd extension skipped: {e}")
finally:
connection.enable_load_extension(False)
connection.row_factory = sqlite3.Row
connection.execute("PRAGMA foreign_keys = ON")
connection.execute("PRAGMA journal_mode = WAL")
connection.execute("PRAGMA synchronous = NORMAL")
@@ -102,6 +115,7 @@ class GameplayDatabase:
self._ensure_column_exists(connection, "games", "your_snake_version", "TEXT")
self._ensure_column_exists(connection, "games", "game_type", "TEXT")
self._ensure_column_exists(connection, "snake_turns", "latency", "TEXT")
if self._zstd_available:
self._enable_zstd_compression(connection)
connection.execute("PRAGMA optimize")
@@ -121,11 +135,27 @@ class GameplayDatabase:
connection.execute(f"CREATE INDEX IF NOT EXISTS {idx_name} ON {table}({cols})")
def _ensure_column_exists(self, connection:sqlite3.Connection, table_name:str, column_name:str, column_type:str) -> None:
existing = connection.execute(f"PRAGMA table_info({table_name})").fetchall()
obj = connection.execute(
"SELECT type FROM sqlite_master WHERE name = ?", (table_name,)
).fetchone()
if obj and obj["type"] == "view":
# zstd replaced this table with a view — operate on the underlying compressed table
underlying = f"_{table_name}_zstd"
exists = connection.execute(
"SELECT 1 FROM sqlite_master WHERE name = ? AND type = 'table'", (underlying,)
).fetchone()
if not exists:
return # nothing we can do without the extension
actual_table = underlying
else:
actual_table = table_name
existing = connection.execute(f"PRAGMA table_info({actual_table})").fetchall()
if any(row["name"] == column_name for row in existing):
return
connection.execute(f"ALTER TABLE {table_name} ADD COLUMN {column_name} {column_type}")
connection.execute(f"ALTER TABLE {actual_table} ADD COLUMN {column_name} {column_type}")
def _enable_zstd_compression(self, connection: sqlite3.Connection) -> None:
compressed_columns = [