package core import ( "embed" "log" _ "github.com/mattn/go-sqlite3" ) //go:embed sql/*.sql var migrations embed.FS // Idempotently initialize the database. Safe to call unconditionally. func InitDatabase(db *DB) error { rows, err := db.Query(` select exists ( select 1 from sqlite_master where type = 'table' and name = 'migrations' ) `) if err != nil { return err } var exists bool for rows.Next() { rows.Scan(&exists) } if exists { return nil } err = ApplyMigration(db, "0000_baseline.sql") return err } // Get a map of migration names to whether the migration has been applied. func GetPendingMigrations(db *DB) (map[string]bool, error) { allMigrations, err := migrations.ReadDir("sql") if err != nil { return nil, err } complete := map[string]bool{} for _, mig := range allMigrations { complete[mig.Name()] = false } rows, err := db.Query("select name from migrations") if err != nil { return nil, err } for rows.Next() { var name string rows.Scan(&name) complete[name] = true } return complete, nil } // Apply a migration by name. func ApplyMigration(db *DB, name string) error { data, err := migrations.ReadFile("sql/" + name) if err != nil { log.Fatalf("Missing migration %s", name) } log.Printf("Applying migration %s", name) _, err = db.Exec(string(data)) if err != nil { return err } _, err = db.Exec("insert into migrations (name) values (?)", name) return err } // Apply all pending migrations. func MigrateDatabase(db *DB) error { pending, err := GetPendingMigrations(db) if err != nil { return err } for name, complete := range pending { if !complete { err = ApplyMigration(db, name) if err != nil { return err } } } return nil }