"""Database migration functions. Each migration is idempotent and safe to run on a database that has already been migrated. Run via run_migration() called from app startup after init_db(). """ import sqlite3 from db import DB_PATH def run_migration() -> None: """Apply all pending schema migrations in order. Currently applies: - v1: Add ai_blocks column to books; clear AI-derived data while preserving user data. - v2: Add batch_queue table for persistent batch processing queue. Migrations are idempotent — running them on an already-migrated database is a no-op. """ c = sqlite3.connect(DB_PATH) c.row_factory = sqlite3.Row c.execute("PRAGMA foreign_keys = ON") try: _migrate_v1(c) _migrate_v2(c) c.commit() except Exception: c.rollback() raise finally: c.close() def _migrate_v1(c: sqlite3.Connection) -> None: """Add ai_blocks column and clear stale AI data from all books (first run only). - Adds ai_blocks TEXT DEFAULT NULL column if it does not exist. - On first run only (when the column is absent): clears raw_text, ai_*, title_confidence, analyzed_at, candidates, ai_blocks from all books (these are regenerated by the new pipeline). - For user_approved books: copies user fields back to ai_* so that compute_status() still returns 'user_approved' after the ai_* clear. This migration assumes the database already has the base books schema. It is a no-op if ai_blocks already exists. """ cols = {row["name"] for row in c.execute("PRAGMA table_info(books)")} if "ai_blocks" not in cols: c.execute("ALTER TABLE books ADD COLUMN ai_blocks TEXT DEFAULT NULL") # Clear AI-derived fields only when first adding the column. c.execute( "UPDATE books SET " "raw_text='', ai_title='', ai_author='', ai_year='', ai_isbn='', ai_publisher='', " "title_confidence=0, analyzed_at=NULL, candidates=NULL, ai_blocks=NULL" ) # For user_approved books, restore ai_* = user fields so status stays user_approved. c.execute( "UPDATE books SET " "ai_title=title, ai_author=author, ai_year=year, ai_isbn=isbn, ai_publisher=publisher " "WHERE identification_status='user_approved'" ) def _migrate_v2(c: sqlite3.Connection) -> None: """Add batch_queue table for persistent batch processing queue. Replaces data/batch_pending.json with a DB table so batch state survives across restarts alongside all other persistent data. """ c.execute("CREATE TABLE IF NOT EXISTS batch_queue (" "book_id TEXT PRIMARY KEY," "added_at REAL NOT NULL" ")")