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
183 lines
4.8 KiB
Go
183 lines
4.8 KiB
Go
package main
|
|
|
|
import (
|
|
"bufio"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"github.com/therootcompany/golib/database/sqlmigrate"
|
|
)
|
|
|
|
func (state *State) parseAndFixupBatches(text string) error {
|
|
text = strings.TrimSpace(text)
|
|
if text == "" {
|
|
return nil
|
|
}
|
|
|
|
fixedUp := []string{}
|
|
fixedDown := []string{}
|
|
|
|
state.Lines = strings.Split(text, "\n")
|
|
for i := range state.Lines {
|
|
line := strings.TrimSpace(state.Lines[i])
|
|
migration := commentStartRe.ReplaceAllString(line, "")
|
|
migration = strings.TrimSpace(migration)
|
|
if migration != "" {
|
|
up, down, warn, err := fixupMigration(state.MigrationsDir, migration)
|
|
if warn != nil {
|
|
fmt.Fprintf(os.Stderr, "Warn: %s\n", warn)
|
|
}
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "%v\n", err)
|
|
}
|
|
state.Migrated = append(state.Migrated, migration)
|
|
if up {
|
|
fixedUp = append(fixedUp, migration)
|
|
}
|
|
if down {
|
|
fixedDown = append(fixedDown, migration)
|
|
}
|
|
|
|
}
|
|
state.Lines[i] = line
|
|
}
|
|
showFixes(fixedUp, fixedDown)
|
|
|
|
return nil
|
|
}
|
|
|
|
func showFixes(fixedUp, fixedDown []string) {
|
|
if len(fixedUp) > 0 {
|
|
fmt.Fprintf(os.Stderr, "Fixup: appended missing 'INSERT INTO _migrations ...' to:\n")
|
|
for _, up := range fixedUp {
|
|
fmt.Fprintf(os.Stderr, " %s\n", up)
|
|
}
|
|
fmt.Fprintf(os.Stderr, "\n")
|
|
}
|
|
|
|
if len(fixedDown) > 0 {
|
|
fmt.Fprintf(os.Stderr, "Fixup: appended missing 'DELETE FROM _migrations ...' to:\n")
|
|
for _, down := range fixedDown {
|
|
fmt.Fprintf(os.Stderr, " %s\n", down)
|
|
}
|
|
fmt.Fprintf(os.Stderr, "\n")
|
|
}
|
|
}
|
|
|
|
// attempts to add missing INSERT and DELETE without breaking what already works
|
|
func fixupMigration(dir string, basename string) (up, down bool, warn error, err error) {
|
|
var id string
|
|
|
|
var insertsOnUp bool
|
|
upPath := filepath.Join(dir, basename+".up.sql")
|
|
upScan, err := os.Open(upPath)
|
|
if err != nil {
|
|
return false, false, nil, fmt.Errorf("failed (up): %w", err)
|
|
}
|
|
defer upScan.Close()
|
|
scanner := bufio.NewScanner(upScan)
|
|
for scanner.Scan() {
|
|
txt := scanner.Text()
|
|
txt = strings.TrimSpace(txt)
|
|
txt = strings.ToLower(txt)
|
|
if strings.HasPrefix(txt, "insert into _migrations") {
|
|
insertsOnUp = true
|
|
break
|
|
}
|
|
}
|
|
if !insertsOnUp {
|
|
id = MustRandomHex(4)
|
|
upScan.Close()
|
|
upBytes, err := os.ReadFile(upPath)
|
|
if err != nil {
|
|
warn = fmt.Errorf("failed to add 'INSERT INTO _migrations ...' to %s: %w", upPath, err)
|
|
return false, false, warn, nil
|
|
}
|
|
|
|
migrationInsertLn := fmt.Sprintf("\n-- leave this as the last line\nINSERT INTO _migrations (name, id) VALUES ('%s', '%s');\n", basename, id)
|
|
upBytes = append(upBytes, []byte(migrationInsertLn)...)
|
|
if err = os.WriteFile(upPath, upBytes, 0644); err != nil {
|
|
warn = fmt.Errorf("failed to append 'INSERT INTO _migrations ...' to %s: %w", upPath, err)
|
|
return false, false, warn, nil
|
|
}
|
|
up = true
|
|
}
|
|
|
|
var deletesOnDown bool
|
|
downPath := filepath.Join(dir, basename+".down.sql")
|
|
downScan, err := os.Open(downPath)
|
|
if err != nil {
|
|
return false, false, fmt.Errorf("failed (down): %w", err), nil
|
|
}
|
|
defer downScan.Close()
|
|
scanner = bufio.NewScanner(downScan)
|
|
for scanner.Scan() {
|
|
txt := scanner.Text()
|
|
txt = strings.TrimSpace(txt)
|
|
txt = strings.ToLower(txt)
|
|
if strings.HasPrefix(txt, "delete from _migrations") {
|
|
deletesOnDown = true
|
|
break
|
|
}
|
|
}
|
|
if !deletesOnDown {
|
|
if id == "" {
|
|
return false, false, fmt.Errorf("must manually append \"DELETE FROM _migrations WHERE id = '<id>'\" to %s with id from %s", downPath, basename+"up.sql"), nil
|
|
}
|
|
downScan.Close()
|
|
downFile, err := os.OpenFile(downPath, os.O_APPEND|os.O_WRONLY, 0o644)
|
|
if err != nil {
|
|
warn = fmt.Errorf("failed to append 'DELETE FROM _migrations ...' to %s: %v", downPath, err)
|
|
return false, false, warn, nil
|
|
}
|
|
defer downFile.Close()
|
|
|
|
migrationInsertLn := fmt.Sprintf("\nDELETE FROM _migrations WHERE id = '%s';\n", id)
|
|
_, err = downFile.Write(([]byte(migrationInsertLn)))
|
|
if err != nil {
|
|
warn = fmt.Errorf("failed to add 'DELETE FROM _migrations ...' to %s: %w", downPath, err)
|
|
return false, false, warn, nil
|
|
}
|
|
down = true
|
|
}
|
|
|
|
return up, down, nil, nil
|
|
}
|
|
|
|
// fixupAll runs fixupMigration on all known migrations (applied + pending).
|
|
func fixupAll(migrationsDir string, applied []string, migrations []sqlmigrate.Script) (fixedUp, fixedDown []string) {
|
|
seen := map[string]bool{}
|
|
var all []string
|
|
for _, name := range applied {
|
|
if !seen[name] {
|
|
all = append(all, name)
|
|
seen[name] = true
|
|
}
|
|
}
|
|
for _, m := range migrations {
|
|
if !seen[m.Name] {
|
|
all = append(all, m.Name)
|
|
seen[m.Name] = true
|
|
}
|
|
}
|
|
|
|
for _, name := range all {
|
|
up, down, warn, err := fixupMigration(migrationsDir, name)
|
|
if warn != nil {
|
|
fmt.Fprintf(os.Stderr, "Warn: %s\n", warn)
|
|
}
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "%v\n", err)
|
|
}
|
|
if up {
|
|
fixedUp = append(fixedUp, name)
|
|
}
|
|
if down {
|
|
fixedDown = append(fixedDown, name)
|
|
}
|
|
}
|
|
return fixedUp, fixedDown
|
|
}
|