package common import ( "context" "database/sql" "embed" "fmt" "log/slog" "path" "regexp" "strconv" "strings" "github.com/nyttikord/avl" ) var regexpMigration = regexp.MustCompile(`(\d{3})-(.*)\.sql`) type migrationData struct { id uint64 name string content string } func Migrate(ctx context.Context, log *slog.Logger, db *sql.DB, migrations embed.FS, dir string) error { entries, err := migrations.ReadDir(dir) if err != nil { return err } tree := avl.NewKeySimple[uint64, migrationData]() for _, entry := range entries { if strings.HasSuffix(entry.Name(), ".sql") { log.Debug("reading migration...", "path", dir+"/"+entry.Name()) subs := regexpMigration.FindStringSubmatch(entry.Name()) if len(subs) < 3 { return fmt.Errorf("invalid migration name %s", entry.Name()) } id, _ := strconv.ParseUint(subs[1], 10, 16) b, err := migrations.ReadFile(path.Join(dir, entry.Name())) if err != nil { return err } tree.Insert(id, migrationData{id, subs[2], string(b)}) } else { log.Warn("invalid migration entry, skipping", "path", dir+"/"+entry.Name()) } } for _, mig := range tree.Sort() { log.Debug("migrating...", "id", mig.id, "name", mig.name) _, err := db.ExecContext(ctx, mig.content) if err != nil { log.Error("migrating", "id", mig.id, "name", mig.name) return err } } return nil }