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
+40 -10
View File
@@ -1,13 +1,21 @@
from datetime import datetime, timezone from datetime import datetime, timezone
import asyncio, sqlite3, json, os import asyncio, sqlite3, json, os, logging, sys
from pathlib import Path 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() _ZSTD_EXT = Path(os.environ.get("SQLITE_ZSTD_EXT", "/usr/local/lib/libsqlite_zstd.so")).expanduser().resolve()
class GameplayDatabase: class GameplayDatabase:
def __init__(self, db_path:str, busy_timeout_ms:int=5000): def __init__(self, db_path:str, busy_timeout_ms:int=5000):
self.db_path = db_path self.db_path = db_path
self.busy_timeout_ms = max(1000, int(busy_timeout_ms)) self.busy_timeout_ms = max(1000, int(busy_timeout_ms))
self._zstd_available = False
self._initialize_database() self._initialize_database()
def _connect(self) -> sqlite3.Connection: def _connect(self) -> sqlite3.Connection:
@@ -16,13 +24,18 @@ class GameplayDatabase:
timeout=max(1, self.busy_timeout_ms // 1000), timeout=max(1, self.busy_timeout_ms // 1000),
isolation_level=None, isolation_level=None,
) )
if Path(_ZSTD_EXT).exists():
connection.enable_load_extension(True)
connection.load_extension(str(_ZSTD_EXT))
connection.enable_load_extension(False)
connection.row_factory = sqlite3.Row connection.row_factory = sqlite3.Row
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.execute("PRAGMA foreign_keys = ON") connection.execute("PRAGMA foreign_keys = ON")
connection.execute("PRAGMA journal_mode = WAL") connection.execute("PRAGMA journal_mode = WAL")
connection.execute("PRAGMA synchronous = NORMAL") connection.execute("PRAGMA synchronous = NORMAL")
@@ -102,7 +115,8 @@ class GameplayDatabase:
self._ensure_column_exists(connection, "games", "your_snake_version", "TEXT") self._ensure_column_exists(connection, "games", "your_snake_version", "TEXT")
self._ensure_column_exists(connection, "games", "game_type", "TEXT") self._ensure_column_exists(connection, "games", "game_type", "TEXT")
self._ensure_column_exists(connection, "snake_turns", "latency", "TEXT") self._ensure_column_exists(connection, "snake_turns", "latency", "TEXT")
self._enable_zstd_compression(connection) if self._zstd_available:
self._enable_zstd_compression(connection)
connection.execute("PRAGMA optimize") connection.execute("PRAGMA optimize")
def _create_indexes_if_tables(self, connection: sqlite3.Connection) -> None: def _create_indexes_if_tables(self, connection: sqlite3.Connection) -> None:
@@ -121,11 +135,27 @@ class GameplayDatabase:
connection.execute(f"CREATE INDEX IF NOT EXISTS {idx_name} ON {table}({cols})") 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: 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): if any(row["name"] == column_name for row in existing):
return 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: def _enable_zstd_compression(self, connection: sqlite3.Connection) -> None:
compressed_columns = [ compressed_columns = [