from sqlalchemy import create_engine, event, text from sqlalchemy.orm import sessionmaker, DeclarativeBase from .config import settings engine = create_engine( settings.database_url, connect_args={"check_same_thread": False}, echo=False, ) @event.listens_for(engine, "connect") def set_sqlite_pragma(dbapi_conn, _): cursor = dbapi_conn.cursor() cursor.execute("PRAGMA journal_mode=WAL") cursor.execute("PRAGMA foreign_keys=ON") cursor.execute("PRAGMA busy_timeout=5000") cursor.execute("PRAGMA synchronous=NORMAL") cursor.close() SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) class Base(DeclarativeBase): pass FTS_SETUP_SQL = """ CREATE VIRTUAL TABLE IF NOT EXISTS videos_fts USING fts5( title, description, content=videos, content_rowid=id ); CREATE VIRTUAL TABLE IF NOT EXISTS channels_fts USING fts5( name, description, content=channels, content_rowid=id ); CREATE TRIGGER IF NOT EXISTS videos_ai AFTER INSERT ON videos BEGIN INSERT INTO videos_fts(rowid, title, description) VALUES (new.id, new.title, COALESCE(new.description, '')); END; CREATE TRIGGER IF NOT EXISTS videos_ad AFTER DELETE ON videos BEGIN INSERT INTO videos_fts(videos_fts, rowid, title, description) VALUES ('delete', old.id, old.title, COALESCE(old.description, '')); END; CREATE TRIGGER IF NOT EXISTS videos_au AFTER UPDATE ON videos BEGIN INSERT INTO videos_fts(videos_fts, rowid, title, description) VALUES ('delete', old.id, old.title, COALESCE(old.description, '')); INSERT INTO videos_fts(rowid, title, description) VALUES (new.id, new.title, COALESCE(new.description, '')); END; CREATE TRIGGER IF NOT EXISTS channels_ai AFTER INSERT ON channels BEGIN INSERT INTO channels_fts(rowid, name, description) VALUES (new.id, new.name, COALESCE(new.description, '')); END; CREATE TRIGGER IF NOT EXISTS channels_ad AFTER DELETE ON channels BEGIN INSERT INTO channels_fts(channels_fts, rowid, name, description) VALUES ('delete', old.id, old.name, COALESCE(old.description, '')); END; CREATE TRIGGER IF NOT EXISTS channels_au AFTER UPDATE ON channels BEGIN INSERT INTO channels_fts(channels_fts, rowid, name, description) VALUES ('delete', old.id, old.name, COALESCE(old.description, '')); INSERT INTO channels_fts(rowid, name, description) VALUES (new.id, new.name, COALESCE(new.description, '')); END; """ def _add_column_if_missing(raw_conn, table: str, column: str, definition: str): existing = {row[1] for row in raw_conn.execute(f"PRAGMA table_info({table})")} if column not in existing: raw_conn.execute(f"ALTER TABLE {table} ADD COLUMN {column} {definition}") def init_db(): from . import models # noqa: F401 Base.metadata.create_all(bind=engine) raw_conn = engine.raw_connection() try: # Column migrations — safe to run on every startup _add_column_if_missing(raw_conn, "videos", "view_count", "INTEGER") _add_column_if_missing(raw_conn, "videos", "like_count", "INTEGER") _add_column_if_missing(raw_conn, "videos", "dislike_count", "INTEGER") raw_conn.commit() # executescript handles multi-statement SQL including trigger BEGIN...END blocks raw_conn.executescript(FTS_SETUP_SQL) finally: raw_conn.close() def get_db(): db = SessionLocal() try: yield db finally: db.close()