package db import ( "database/sql" "fmt" "log" _ "github.com/mattn/go-sqlite3" ) // DB wraps the SQL database connection. type DB struct { *sql.DB } // Open opens a SQLite database and runs migrations. func Open(path string) (*DB, error) { sqlDB, err := sql.Open("sqlite3", path+"?_journal_mode=WAL&_foreign_keys=on") if err != nil { return nil, fmt.Errorf("opening database: %w", err) } if err := sqlDB.Ping(); err != nil { return nil, fmt.Errorf("pinging database: %w", err) } db := &DB{sqlDB} if err := db.migrate(); err != nil { return nil, fmt.Errorf("running migrations: %w", err) } return db, nil } func (db *DB) migrate() error { // Ensure schema_migrations table exists _, err := db.Exec(`CREATE TABLE IF NOT EXISTS schema_migrations ( version INTEGER PRIMARY KEY, applied_at DATETIME DEFAULT CURRENT_TIMESTAMP )`) if err != nil { return err } // Get current version var currentVersion int row := db.QueryRow("SELECT COALESCE(MAX(version), 0) FROM schema_migrations") if err := row.Scan(¤tVersion); err != nil { return err } // Apply new migrations for i := currentVersion; i < len(migrations); i++ { log.Printf("Applying migration %d...", i+1) if _, err := db.Exec(migrations[i]); err != nil { return fmt.Errorf("migration %d: %w", i+1, err) } if _, err := db.Exec("INSERT INTO schema_migrations (version) VALUES (?)", i+1); err != nil { return fmt.Errorf("recording migration %d: %w", i+1, err) } } return nil }