159 lines
4.9 KiB
Python
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,
|
|
'ETL jobs, match imports and active lineup 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
|