package main import ( "database/sql" _ "modernc.org/sqlite" ) var db *sql.DB const schema = ` CREATE TABLE IF NOT EXISTS user ( id INTEGER PRIMARY KEY AUTOINCREMENT, uid INTEGER UNIQUE, username TEXT UNIQUE, password BLOB, created_at INTEGER, last_login INTEGER, avatar TEXT NOT NULL DEFAULT '' ); CREATE TABLE IF NOT EXISTS session ( id INTEGER PRIMARY KEY AUTOINCREMENT, uid INTEGER, value TEXT UNIQUE, created_at INTEGER, expires INTEGER, description TEXT ); CREATE TABLE IF NOT EXISTS entry ( id INTEGER PRIMARY KEY AUTOINCREMENT, pid INTEGER UNIQUE NOT NULL, uid INTEGER NOT NULL, created_at INTEGER NOT NULL, content TEXT NOT NULL DEFAULT '', filepath TEXT NOT NULL DEFAULT '', reply_to INTEGER NOT NULL DEFAULT 0, reply_count INTEGER NOT NULL DEFAULT 0, last_activity INTEGER NOT NULL DEFAULT 0, deleted INTEGER NOT NULL DEFAULT 0 ); CREATE TABLE IF NOT EXISTS vote ( id INTEGER PRIMARY KEY AUTOINCREMENT, uid INTEGER, pid INTEGER, mode TEXT, UNIQUE(uid, pid) ); -- Indizes für die häufigen Zugriffspfade; ohne sie werden Feed (ORDER BY -- last_activity), Thread (WHERE reply_to) und Vote-Zähler (WHERE pid) mit -- wachsender Tabelle zu Full-Table-Scans. username/session.value/vote(uid,pid) -- sind über UNIQUE bereits indiziert. CREATE INDEX IF NOT EXISTS idx_entry_last_activity ON entry(last_activity); CREATE INDEX IF NOT EXISTS idx_entry_reply_to ON entry(reply_to); CREATE INDEX IF NOT EXISTS idx_entry_uid ON entry(uid); CREATE INDEX IF NOT EXISTS idx_vote_pid ON vote(pid); CREATE INDEX IF NOT EXISTS idx_session_uid ON session(uid); ` func initDB(dsn string) error { var err error // SQLite hat genau einen Schreiber: busy_timeout wartet bei Sperren, statt // sofort mit SQLITE_BUSY zu scheitern; WAL erlaubt parallele Leser. db, err = sql.Open("sqlite", dsn+"?_pragma=busy_timeout(5000)&_pragma=journal_mode(WAL)&_pragma=foreign_keys(on)") if err != nil { return err } if err = db.Ping(); err != nil { return err } _, err = db.Exec(schema) return err }