package main import ( "database/sql" "embed" "fmt" "io/fs" "log" "path/filepath" ) const ( migrationDir = `sql/migrations` migrationFilePattern = `%04d.sqlite3` ) var migrations []string //go:embed sql/migrations/*.sqlite3 var migrationsFS embed.FS func init() { readMigrations() } func readMigrations() { i := 1 _ = fs.WalkDir(migrationsFS, migrationDir, func(path string, d fs.DirEntry, err error) error { if !d.IsDir() { expected := fmt.Sprintf(migrationFilePattern, i) if filepath.Base(path) == expected { data, err := fs.ReadFile(migrationsFS, path) if err != nil { log.Fatalf("Could not read migration: %v", err) } migrations = append(migrations, string(data)) i += 1 } } return nil }) } func Transaction(db *sql.DB, f func(*sql.Tx) error) error { tx, err := db.Begin() if err != nil { return err } defer tx.Rollback() err = f(tx) if err != nil { return err } return tx.Commit() } func Migrate(db *sql.DB, index int) error { return Transaction(db, func(tx *sql.Tx) error { _, err := tx.Exec(migrations[index]) return err }) } func UserVersion(db *sql.DB) (int, error) { var version int err := Transaction(db, func(tx *sql.Tx) error { return tx.QueryRow(`PRAGMA user_version`).Scan(&version) }) if err != nil { return 0, err } return version, nil } func MigrateToLatest(db *sql.DB) error { for i := range migrations { v, err := UserVersion(db) if err != nil { return fmt.Errorf("Failed UserVersion() inside MigrateToLatest(): %w", err) } if i == v { err = Migrate(db, i) if err != nil { return fmt.Errorf("Failed Migrate() inside MigrateToLatest(): %w", err) } } } return nil }