"""SQLite schema for compact gameplay storage (mirrors postgresql_schema).""" import sqlite3 BASE_TABLES = """ CREATE TABLE IF NOT EXISTS games ( game_id TEXT PRIMARY KEY, started_at TEXT NOT NULL, ended_at TEXT, width INTEGER, height INTEGER, source TEXT, map_name TEXT, ruleset_name TEXT, ruleset_version TEXT, your_snake_id TEXT, your_snake_name TEXT, your_snake_type TEXT, your_snake_version TEXT, game_type TEXT, winner_name TEXT, winner_you INTEGER NOT NULL DEFAULT 0, final_turn INTEGER NOT NULL DEFAULT 0, status TEXT NOT NULL DEFAULT 'running', has_replay INTEGER NOT NULL DEFAULT 1, quality_status TEXT NOT NULL DEFAULT 'retained', quality_score INTEGER, quality_tier TEXT, quality_reasons_json TEXT, gid INTEGER ); CREATE TABLE IF NOT EXISTS game_snakes ( game_id TEXT NOT NULL, snake_id TEXT NOT NULL, snake_name TEXT, is_you INTEGER NOT NULL DEFAULT 0, customizations_json TEXT NOT NULL DEFAULT '{}', gsid INTEGER, PRIMARY KEY (game_id, snake_id), FOREIGN KEY (game_id) REFERENCES games(game_id) ON DELETE CASCADE ); """ # Columns added over time; older databases get them via ALTER TABLE. LEGACY_COLUMNS = ( ("games", "your_snake_type", "TEXT"), ("games", "your_snake_version", "TEXT"), ("games", "game_type", "TEXT"), ("games", "winner_name", "TEXT"), ("games", "has_replay", "INTEGER NOT NULL DEFAULT 1"), ("games", "quality_status", "TEXT NOT NULL DEFAULT 'retained'"), ("games", "quality_score", "INTEGER"), ("games", "quality_tier", "TEXT"), ("games", "quality_reasons_json", "TEXT"), ("games", "gid", "INTEGER"), ("game_snakes", "customizations_json", "TEXT NOT NULL DEFAULT '{}'"), ("game_snakes", "gsid", "INTEGER"), ) COMPACT_TABLES = """ CREATE UNIQUE INDEX IF NOT EXISTS games_gid_key ON games(gid); CREATE UNIQUE INDEX IF NOT EXISTS game_snakes_gsid_key ON game_snakes(gsid); CREATE INDEX IF NOT EXISTS idx_games_status ON games(status); UPDATE games SET gid = rowid + (SELECT COALESCE(MAX(gid), 0) FROM games) WHERE gid IS NULL; UPDATE game_snakes SET gsid = rowid + (SELECT COALESCE(MAX(gsid), 0) FROM game_snakes) WHERE gsid IS NULL; CREATE TABLE IF NOT EXISTS game_turns ( gid INTEGER NOT NULL REFERENCES games(gid) ON DELETE CASCADE, turn INTEGER NOT NULL, observed_at TEXT NOT NULL, my_move INTEGER, thinking_json TEXT, food BLOB, hazards BLOB, PRIMARY KEY (gid, turn) ) WITHOUT ROWID; CREATE TABLE IF NOT EXISTS snake_states ( gsid INTEGER NOT NULL REFERENCES game_snakes(gsid) ON DELETE CASCADE, turn INTEGER NOT NULL, health INTEGER, latency INTEGER, body BLOB NOT NULL, PRIMARY KEY (gsid, turn) ) WITHOUT ROWID; CREATE TABLE IF NOT EXISTS game_replays ( gid INTEGER PRIMARY KEY REFERENCES games(gid) ON DELETE CASCADE, format INTEGER NOT NULL, turn_count INTEGER NOT NULL, data BLOB NOT NULL ); DROP INDEX IF EXISTS idx_turns_game_turn; DROP INDEX IF EXISTS idx_snake_turns_game_turn; """ def ensure_column(connection:sqlite3.Connection, table:str, column:str, column_type:str) -> None: existing = {row[1] for row in connection.execute(f"PRAGMA table_info({table})")} if column not in existing: connection.execute(f"ALTER TABLE {table} ADD COLUMN {column} {column_type}") def create_schema(connection:sqlite3.Connection) -> None: connection.executescript(BASE_TABLES) for table, column, column_type in LEGACY_COLUMNS: ensure_column(connection, table, column, column_type) connection.executescript(COMPACT_TABLES)