78 lines
1.8 KiB
Go
78 lines
1.8 KiB
Go
package database
|
|
|
|
import (
|
|
"fmt"
|
|
"io/fs"
|
|
"slices"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
// Atomically run migrations found from the given filesystem (with the format `<SERIAL_NUM>*.up.sql`).
|
|
// Skips already applied migrations, but fails on malformed or iisconfigured entries. Notably atomic
|
|
// execution must be utilized to automatically roll back any partially applied migrations.
|
|
func RunMigrations(db *gorm.DB, migrationsFS fs.FS) error {
|
|
return db.Transaction(func(tx *gorm.DB) error {
|
|
if err := tx.Exec(`
|
|
CREATE TABLE IF NOT EXISTS schema_migrations (
|
|
version INTEGER PRIMARY KEY
|
|
)
|
|
`).Error; err != nil {
|
|
return err
|
|
}
|
|
|
|
// Query already applied migrations to prevent duplicate execution
|
|
var applied []int
|
|
if err := tx.Table("schema_migrations").Pluck("version", &applied).Error; err != nil {
|
|
return err
|
|
}
|
|
|
|
migrationFiles, err := fs.Glob(migrationsFS, "*.up.sql")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for _, f := range sortMigrations(migrationFiles) {
|
|
version, err := strconv.Atoi(strings.Split(f, "_")[0])
|
|
if err != nil {
|
|
return fmt.Errorf("invalid migration filename: %s", f)
|
|
}
|
|
|
|
if slices.Contains(applied, version) {
|
|
continue
|
|
}
|
|
|
|
sql, err := fs.ReadFile(migrationsFS, f)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := tx.Exec(string(sql)).Error; err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := tx.Exec(`
|
|
INSERT INTO schema_migrations (version) VALUES (?)
|
|
`, version).Error; err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
})
|
|
}
|
|
|
|
// Sort the given migration files to ascending order based on the filename integer prefix to then
|
|
// be executed sequentially.
|
|
func sortMigrations(files []string) []string {
|
|
sort.Slice(files, func(i, j int) bool {
|
|
v1, _ := strconv.Atoi(strings.Split(files[i], "_")[0])
|
|
v2, _ := strconv.Atoi(strings.Split(files[j], "_")[0])
|
|
return v1 < v2
|
|
})
|
|
return files
|
|
}
|