ref(cmd/sql-migrate/v2): make INSERTs sortable, output cleanup

This commit is contained in:
AJ ONeal 2026-01-31 01:18:58 -07:00
parent 10236de205
commit 1ec145b3eb
No known key found for this signature in database

View File

@ -29,7 +29,7 @@ import (
) )
const ( const (
version = "2.0.0" version = "2.0.2"
) )
const ( const (
@ -53,13 +53,15 @@ CREATE TABLE IF NOT EXISTS _migrations (
applied_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP applied_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
); );
INSERT INTO _migrations (id, name) VALUES ('00000001', '0001-01-01-01000_init-migrations'); -- note: to enable text-based tools to grep and sort we put 'name' before 'id'
-- grep -r 'INSERT INTO _migrations' ./sql/migrations/ | cut -d':' -f2 | sort
INSERT INTO _migrations (name, id) VALUES ('0001-01-01-01000_init-migrations', '00000001');
` `
defaultMigratorDown = `DELETE FROM _migrations WHERE id = '00000001'; defaultMigratorDown = `DELETE FROM _migrations WHERE id = '00000001';
DROP TABLE IF EXISTS _migrations; DROP TABLE IF EXISTS _migrations;
` `
LOG_MIGRATIONS_QUERY = `-- note: --no-align must be passed via CLI to avoid extraneous output LOG_MIGRATIONS_QUERY = `-- note: CLI arguments must be passed to the sql command to keep output clean
SELECT name FROM _migrations ORDER BY name; SELECT name FROM _migrations ORDER BY name;
` `
shHeader = `#/bin/sh shHeader = `#/bin/sh
@ -369,7 +371,7 @@ func migrationsList(migrationsDir string, entries []os.DirEntry) (ups, downs []s
name := entry.Name() name := entry.Name()
if strings.HasPrefix(name, ".") || strings.HasPrefix(name, "_") { if strings.HasPrefix(name, ".") || strings.HasPrefix(name, "_") {
if name != LOG_QUERY_NAME { if name != LOG_QUERY_NAME {
fmt.Fprintf(os.Stderr, " ignoring '%s'\n", filepathUnclean(filepath.Join(migrationsDir, name))) fmt.Fprintf(os.Stderr, " ignoring %s\n", filepathJoin(migrationsDir, name))
} }
continue continue
} }
@ -385,7 +387,7 @@ func migrationsList(migrationsDir string, entries []os.DirEntry) (ups, downs []s
continue continue
} }
fmt.Fprintf(os.Stderr, " unknown '%s'\n", filepath.Join(migrationsDir, name)) fmt.Fprintf(os.Stderr, " unknown %s\n", filepathJoin(migrationsDir, name))
} }
for _, down := range downs { for _, down := range downs {
// TODO on downs add INSERT to file and to up migration if it doesn't exist // TODO on downs add INSERT to file and to up migration if it doesn't exist
@ -424,6 +426,10 @@ func filepathUnclean(path string) string {
return path return path
} }
func filepathJoin(src, dst string) string {
return filepathUnclean(filepath.Join(src, dst))
}
// initializes all necessary files and directories // initializes all necessary files and directories
// - ./sql/migrations.log // - ./sql/migrations.log
// //
@ -466,7 +472,7 @@ func mustInit(cfg *MainConfig) {
// write config // write config
if slices.Contains(ups, M_MIGRATOR_NAME) { if slices.Contains(ups, M_MIGRATOR_NAME) {
fmt.Fprintf(os.Stderr, " found '%s'\n", filepath.Join(cfg.migrationsDir, M_MIGRATOR_UP_NAME)) fmt.Fprintf(os.Stderr, " found %s\n", filepath.Join(cfg.migrationsDir, M_MIGRATOR_UP_NAME))
} else { } else {
if cfg.logPath == "" { if cfg.logPath == "" {
migrationsParent := filepath.Dir(cfg.migrationsDir) migrationsParent := filepath.Dir(cfg.migrationsDir)
@ -483,7 +489,7 @@ func mustInit(cfg *MainConfig) {
fmt.Fprintf(os.Stderr, "Error: init couldn't create initial up migration: %v\n", err) fmt.Fprintf(os.Stderr, "Error: init couldn't create initial up migration: %v\n", err)
os.Exit(1) os.Exit(1)
} else if created { } else if created {
fmt.Fprintf(os.Stderr, " created '%s'\n", mMigratorUpPath) fmt.Fprintf(os.Stderr, " created %s\n", filepathUnclean(mMigratorUpPath))
} }
} }
@ -511,14 +517,14 @@ func mustInit(cfg *MainConfig) {
} }
if slices.Contains(downs, M_MIGRATOR_NAME) { if slices.Contains(downs, M_MIGRATOR_NAME) {
fmt.Fprintf(os.Stderr, " found '%s'\n", mMigratorDownPath) fmt.Fprintf(os.Stderr, " found %s\n", filepathUnclean(mMigratorDownPath))
} else { } else {
migratorDownQuery := defaultMigratorDown migratorDownQuery := defaultMigratorDown
if created, err := initFile(mMigratorDownPath, migratorDownQuery); err != nil { if created, err := initFile(mMigratorDownPath, migratorDownQuery); err != nil {
fmt.Fprintf(os.Stderr, "Error: init couldn't create initial up migration: %v\n", err) fmt.Fprintf(os.Stderr, "Error: init couldn't create initial up migration: %v\n", err)
os.Exit(1) os.Exit(1)
} else if created { } else if created {
fmt.Fprintf(os.Stderr, " created '%s'\n", mMigratorDownPath) fmt.Fprintf(os.Stderr, " created %s\n", filepathUnclean(mMigratorDownPath))
} }
} }
@ -527,13 +533,13 @@ func mustInit(cfg *MainConfig) {
fmt.Fprintf(os.Stderr, "Error: init couldn't create migrations query: %v\n", err) fmt.Fprintf(os.Stderr, "Error: init couldn't create migrations query: %v\n", err)
os.Exit(1) os.Exit(1)
} else if created { } else if created {
fmt.Fprintf(os.Stderr, " created '%s'\n", logQueryPath) fmt.Fprintf(os.Stderr, " created %s\n", filepathUnclean(logQueryPath))
} else { } else {
fmt.Fprintf(os.Stderr, " found '%s'\n", logQueryPath) fmt.Fprintf(os.Stderr, " found %s\n", filepathUnclean(logQueryPath))
} }
if fileExists(state.LogPath) { if fileExists(state.LogPath) {
fmt.Fprintf(os.Stderr, " found '%s'\n", state.LogPath) fmt.Fprintf(os.Stderr, " found %s\n", filepathUnclean(state.LogPath))
fmt.Fprintf(os.Stderr, "done\n") fmt.Fprintf(os.Stderr, "done\n")
return return
} }
@ -727,15 +733,15 @@ func create(state *State, desc string) error {
// Little Bobby Drop Tables says: // Little Bobby Drop Tables says:
// We trust the person running the migrations to not use malicious names. // 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) // (we don't want to embed db-specific logic here, and SQL doesn't define escaping)
migrationInsert := fmt.Sprintf("INSERT INTO _migrations (id, name) VALUES ('%s', '%s');", id, basename) migrationInsert := fmt.Sprintf("INSERT INTO _migrations (name, id) VALUES ('%s', '%s');", basename, id)
upContent := fmt.Appendf(nil, "-- leave this as the first line\n%s\n\n-- %s (up)\nSELECT 'place your UP migration here';\n", migrationInsert, desc) upContent := fmt.Appendf(nil, "-- leave this as the first line\n%s\n\n-- %s (up)\nSELECT 'place your UP migration here';\n", migrationInsert, desc)
_ = os.WriteFile(upPath, upContent, 0644) _ = os.WriteFile(upPath, upContent, 0644)
migrationDelete := fmt.Sprintf("DELETE FROM _migrations WHERE id = '%s';", id) 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) 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)
_ = os.WriteFile(downPath, downContent, 0644) _ = os.WriteFile(downPath, downContent, 0644)
fmt.Fprintf(os.Stderr, " created pair %s\n", upPath) fmt.Fprintf(os.Stderr, " created pair %s\n", filepathUnclean(upPath))
fmt.Fprintf(os.Stderr, " %s\n", downPath) fmt.Fprintf(os.Stderr, " %s\n", filepathUnclean(downPath))
return nil return nil
} }
@ -786,7 +792,7 @@ func fixupMigration(dir string, basename string) (up, down bool, warn error, err
return false, false, warn, nil return false, false, warn, nil
} }
migrationInsertLn := fmt.Sprintf("INSERT INTO _migrations (id, name) VALUES ('%s', '%s');\n\n", id, basename) migrationInsertLn := fmt.Sprintf("INSERT INTO _migrations (name, id) VALUES ('%s', '%s');\n\n", basename, id)
upBytes = append([]byte(migrationInsertLn), upBytes...) upBytes = append([]byte(migrationInsertLn), upBytes...)
if err = os.WriteFile(upPath, upBytes, 0644); err != nil { if err = os.WriteFile(upPath, upBytes, 0644); err != nil {
warn = fmt.Errorf("failed to prepend 'INSERT INTO _migrations ...' to %s: %w", upPath, err) warn = fmt.Errorf("failed to prepend 'INSERT INTO _migrations ...' to %s: %w", upPath, err)
@ -853,7 +859,7 @@ func up(state *State, ups []string, n int) error {
fmt.Fprintf(os.Stderr, "# Already up-to-date\n") fmt.Fprintf(os.Stderr, "# Already up-to-date\n")
fmt.Fprintf(os.Stderr, "#\n") fmt.Fprintf(os.Stderr, "#\n")
fmt.Fprintf(os.Stderr, "# To reload the migrations log:\n") fmt.Fprintf(os.Stderr, "# To reload the migrations log:\n")
fmt.Fprintf(os.Stderr, "%s\n", "# "+getMigs+" > "+filepathUnclean(state.LogPath)) fmt.Fprintf(os.Stderr, "# %s > %s\n", getMigs, filepathUnclean(state.LogPath))
return nil return nil
} }
if n == 0 { if n == 0 {
@ -914,7 +920,7 @@ func down(state *State, n int) error {
fmt.Fprintf(os.Stderr, "# No migration history\n") fmt.Fprintf(os.Stderr, "# No migration history\n")
fmt.Fprintf(os.Stderr, "#\n") fmt.Fprintf(os.Stderr, "#\n")
fmt.Fprintf(os.Stderr, "# To reload the migrations log:\n") fmt.Fprintf(os.Stderr, "# To reload the migrations log:\n")
fmt.Fprintf(os.Stderr, "%s\n", "# "+getMigs+" > "+filepathUnclean(state.LogPath)) fmt.Fprintf(os.Stderr, "# %s > %s\n", getMigs, filepathUnclean(state.LogPath))
return nil return nil
} }
if n == 0 { if n == 0 {
@ -947,7 +953,7 @@ func down(state *State, n int) error {
cmd := strings.Replace(state.SQLCommand, "%s", downPath, 1) cmd := strings.Replace(state.SQLCommand, "%s", downPath, 1)
fmt.Printf("\n# -%d %s\n", i+1, migration) fmt.Printf("\n# -%d %s\n", i+1, migration)
if !fileExists(downPath) { if !fileExists(downPath) {
fmt.Fprintf(os.Stderr, "# Warn: missing '%s'\n", downPath) fmt.Fprintf(os.Stderr, "# Warn: missing %s\n", filepathUnclean(downPath))
fmt.Fprintf(os.Stderr, "# (the migration will fail to run)\n") fmt.Fprintf(os.Stderr, "# (the migration will fail to run)\n")
fmt.Printf("# ERROR: MISSING FILE\n") fmt.Printf("# ERROR: MISSING FILE\n")
} else { } else {
@ -981,8 +987,8 @@ func status(state *State, ups []string) error {
copy(previous, state.Lines) copy(previous, state.Lines)
slices.Reverse(previous) slices.Reverse(previous)
fmt.Fprintf(os.Stderr, "migrations_dir: %s\n", state.MigrationsDir) fmt.Fprintf(os.Stderr, "migrations_dir: %s\n", filepathUnclean(state.MigrationsDir))
fmt.Fprintf(os.Stderr, "migrations_log: %s\n", state.LogPath) fmt.Fprintf(os.Stderr, "migrations_log: %s\n", filepathUnclean(state.LogPath))
fmt.Fprintf(os.Stderr, "sql_command: %s\n", state.SQLCommand) fmt.Fprintf(os.Stderr, "sql_command: %s\n", state.SQLCommand)
fmt.Fprintf(os.Stderr, "\n") fmt.Fprintf(os.Stderr, "\n")
fmt.Printf("# previous: %d\n", len(previous)) fmt.Printf("# previous: %d\n", len(previous))