package bootdb import ( "database/sql" "embed" "fmt" "log/slog" "github.com/golang-migrate/migrate/v4" "github.com/golang-migrate/migrate/v4/database/sqlite3" "github.com/golang-migrate/migrate/v4/source/iofs" _ "github.com/mattn/go-sqlite3" ) func Database(path string) (*sql.DB, error) { db, err := sql.Open("sqlite3", path) if err != nil { return nil, fmt.Errorf("opening database: %w", err) } if err = db.Ping(); err != nil { return nil, fmt.Errorf("pinging database: %w", err) } // Enable WAL mode and foreign keys for SQLite. _, err = db.Exec("PRAGMA journal_mode=WAL") if err != nil { return nil, fmt.Errorf("setting journal mode: %w", err) } _, err = db.Exec("PRAGMA foreign_keys=ON") if err != nil { return nil, fmt.Errorf("enabling foreign keys: %w", err) } slog.Info("Database connected", "path", path) err = runMigrations(db) if err != nil { return nil, err } // TODO revisit concurrency. // running any sqlite queries outside of a tx seems to run into "database is locked" errors. db.SetMaxOpenConns(1) return db, nil } //go:embed migrations/*.sql var migrations embed.FS func runMigrations(db *sql.DB) error { source, err := iofs.New(migrations, "migrations") if err != nil { return fmt.Errorf("creating migration source: %w", err) } driver, err := sqlite3.WithInstance(db, &sqlite3.Config{}) if err != nil { return fmt.Errorf("creating migration driver: %w", err) } m, err := migrate.NewWithInstance("iofs", source, "sqlite3", driver) if err != nil { return fmt.Errorf("creating migration instance: %w", err) } if err := m.Up(); err != nil && err != migrate.ErrNoChange { return fmt.Errorf("running migrations: %w", err) } slog.Info("Migrations completed") return nil }