mirror of
https://github.com/therootcompany/golib.git
synced 2026-11-04 06:55:00 +00:00
- Add ErrMissingScript sentinel for unknown applied migrations in Down (previously misused ErrMissingDown when entire script was missing) - Wrap Applied() errors with ErrQueryApplied in Up, Down, GetStatus - Check ctx.Err() between migration loop iterations in Up and Down - Tighten litemigrate error match to "no such table: _migrations" - Return error from logMigrationsSelect instead of calling os.Exit(1) - Check os.WriteFile errors in create command - Split main.go (1147 lines) into main.go, init.go, fixup.go, create.go
111 lines
2.9 KiB
Go
111 lines
2.9 KiB
Go
package main
|
|
|
|
import (
|
|
"crypto/rand"
|
|
"encoding/hex"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
func create(state *State, desc string) error {
|
|
dateStr := state.Date.Format("2006-01-02")
|
|
entries, err := os.ReadDir(state.MigrationsDir)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
maxNumber := 0
|
|
datePrefix := dateStr + "-"
|
|
for _, entry := range entries {
|
|
if entry.IsDir() {
|
|
continue
|
|
}
|
|
|
|
name := entry.Name()
|
|
if !strings.HasPrefix(name, datePrefix) {
|
|
continue
|
|
}
|
|
if !strings.HasSuffix(name, ".up.sql") {
|
|
continue
|
|
}
|
|
if strings.HasSuffix(name, "_"+desc+".up.sql") {
|
|
return fmt.Errorf("migration for %q already exists:\n %s", desc, state.MigrationsDir+"/"+name)
|
|
}
|
|
if strings.HasSuffix(name, ".down.sql") {
|
|
continue
|
|
}
|
|
|
|
parts := strings.SplitN(name, "-", 4)
|
|
if len(parts) < 4 {
|
|
continue
|
|
}
|
|
numDesc := strings.SplitN(parts[3], "_", 2)
|
|
if len(numDesc) < 2 {
|
|
continue
|
|
}
|
|
num, err := strconv.Atoi(numDesc[0])
|
|
if err != nil {
|
|
continue
|
|
}
|
|
|
|
if num > maxNumber {
|
|
maxNumber = num
|
|
}
|
|
}
|
|
|
|
number := maxNumber / 1_000
|
|
number *= 1_000
|
|
number += 1_000
|
|
if number > 9_000 && number < 10_000 {
|
|
fmt.Fprintf(os.Stderr, "Achievement Unlocked: It's over 9000!\n")
|
|
}
|
|
if number >= 999_999 {
|
|
fmt.Fprintf(os.Stderr, "Error: cowardly refusing to generate such a suspiciously high number of migrations after running out of numbers\n")
|
|
os.Exit(1)
|
|
}
|
|
|
|
basename := fmt.Sprintf("%s-%06d_%s", dateStr, number, desc)
|
|
upPath := filepath.Join(state.MigrationsDir, basename+".up.sql")
|
|
downPath := filepath.Join(state.MigrationsDir, basename+".down.sql")
|
|
|
|
id := MustRandomHex(4)
|
|
|
|
// Little Bobby Drop Tables says:
|
|
// We trust the person running the migrations to not use malicious names.
|
|
// (we don't want to embed db-specific logic here, and SQL doesn't define escaping)
|
|
migrationInsert := fmt.Sprintf("INSERT INTO _migrations (name, id) VALUES ('%s', '%s');", basename, id)
|
|
upContent := fmt.Appendf(nil, "-- %s (up)\nSELECT 'place your UP migration here';\n\n-- leave this as the last line\n%s\n", desc, migrationInsert)
|
|
if err := os.WriteFile(upPath, upContent, 0644); err != nil {
|
|
return fmt.Errorf("create up migration: %w", err)
|
|
}
|
|
migrationDelete := fmt.Sprintf("DELETE FROM _migrations WHERE id = '%s';", id)
|
|
downContent := fmt.Appendf(nil, "-- %s (down)\nSELECT 'place your DOWN migration here';\n\n-- leave this as the last line\n%s\n", desc, migrationDelete)
|
|
if err := os.WriteFile(downPath, downContent, 0644); err != nil {
|
|
return fmt.Errorf("create down migration: %w", err)
|
|
}
|
|
|
|
fmt.Fprintf(os.Stderr, " created pair %s\n", filepathUnclean(upPath))
|
|
fmt.Fprintf(os.Stderr, " %s\n", filepathUnclean(downPath))
|
|
return nil
|
|
}
|
|
|
|
func MustRandomHex(n int) string {
|
|
s, err := RandomHex(n)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return s
|
|
}
|
|
|
|
func RandomHex(n int) (string, error) {
|
|
b := make([]byte, n) // 4 bytes = 8 hex chars
|
|
_, err := rand.Read(b)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return hex.EncodeToString(b), nil
|
|
}
|