Files
JKTV-online/web/database.py
T

159 lines
4.9 KiB
Python

import os
import logging
import sqlite3
import time
from flask import g
from web.config import Config
logger = logging.getLogger(__name__)
def _database_path(db_name):
paths = {
'l2': Config.DB_L2_PATH,
'l3': Config.DB_L3_PATH,
'web': Config.DB_WEB_PATH,
}
try:
return paths[db_name]
except KeyError as exc:
raise ValueError(f"Unknown database: {db_name}") from exc
def initialize_web_db():
"""Create and migrate the small application-owned database."""
os.makedirs(os.path.dirname(Config.DB_WEB_PATH), exist_ok=True)
db = sqlite3.connect(
Config.DB_WEB_PATH,
timeout=Config.SQLITE_TIMEOUT_SECONDS,
)
try:
table_exists = db.execute(
"SELECT 1 FROM sqlite_master WHERE type='table' AND name='team_lineups'"
).fetchone()
if table_exists:
columns = {
row[1] for row in db.execute("PRAGMA table_info(team_lineups)")
}
if 'is_active' not in columns:
db.execute(
"ALTER TABLE team_lineups "
"ADD COLUMN is_active INTEGER NOT NULL DEFAULT 0"
)
first_id = db.execute(
"SELECT id FROM team_lineups "
"ORDER BY created_at DESC, id DESC LIMIT 1"
).fetchone()
if first_id:
db.execute(
"UPDATE team_lineups SET is_active = 1 WHERE id = ?",
first_id,
)
else:
active_ids = [
row[0] for row in db.execute(
"SELECT id FROM team_lineups WHERE is_active = 1 "
"ORDER BY created_at DESC, id DESC"
)
]
if not active_ids:
latest_id = db.execute(
"SELECT id FROM team_lineups "
"ORDER BY created_at DESC, id DESC LIMIT 1"
).fetchone()
if latest_id:
db.execute(
"UPDATE team_lineups SET is_active = 1 WHERE id = ?",
latest_id,
)
elif len(active_ids) > 1:
db.execute("UPDATE team_lineups SET is_active = 0")
db.execute(
"UPDATE team_lineups SET is_active = 1 WHERE id = ?",
[active_ids[0]],
)
with open(Config.DB_WEB_SCHEMA_PATH, 'r', encoding='utf-8') as schema_file:
db.executescript(schema_file.read())
db.execute(
"""
INSERT OR IGNORE INTO schema_migrations (version, description)
VALUES (?, ?)
""",
[
Config.WEB_SCHEMA_VERSION,
'Roster versions, member roles and team performance governance',
],
)
db.commit()
finally:
db.close()
def get_db(db_name):
"""
db_name: 'l2', 'l3', or 'web'
"""
db_attr = f'db_{db_name}'
db = getattr(g, db_attr, None)
if db is None:
path = _database_path(db_name)
if db_name != 'web' and not os.path.exists(path):
raise RuntimeError(
f"{db_name.upper()} database does not exist: {path}. "
"Run the corresponding data builder first."
)
db = sqlite3.connect(
path,
timeout=Config.SQLITE_TIMEOUT_SECONDS,
)
db.row_factory = sqlite3.Row
db.execute("PRAGMA busy_timeout = 15000")
if db_name != 'l3':
db.execute("PRAGMA foreign_keys = ON")
setattr(g, db_attr, db)
return db
def close_dbs(e=None):
for db_name in ['l2', 'l3', 'web']:
db_attr = f'db_{db_name}'
db = getattr(g, db_attr, None)
if db is not None:
db.close()
def query_db(db_name, query, args=(), one=False):
started = time.perf_counter()
cur = get_db(db_name).execute(query, args)
try:
rv = cur.fetchall()
finally:
cur.close()
duration = time.perf_counter() - started
if duration >= Config.SLOW_QUERY_THRESHOLD_SECONDS:
logger.warning(
"Slow query db=%s duration=%.3fs sql=%s",
db_name,
duration,
" ".join(query.split())[:500],
)
return (rv[0] if rv else None) if one else rv
def execute_db(db_name, query, args=()):
db = get_db(db_name)
started = time.perf_counter()
cur = db.execute(query, args)
db.commit()
duration = time.perf_counter() - started
if duration >= Config.SLOW_QUERY_THRESHOLD_SECONDS:
logger.warning(
"Slow write db=%s duration=%.3fs sql=%s",
db_name,
duration,
" ".join(query.split())[:500],
)
cur.close()
return cur.lastrowid