From f89880da03db221df90d4d0c4caba37791708d62 Mon Sep 17 00:00:00 2001 From: Liam Staskawicz Date: Sun, 6 Jan 2013 22:47:45 -0800 Subject: [PATCH] go migrations: much simpler and more robust way to execute migrations. go migrations are all 'package main', and the version is simply appended to the function name in order to distinguish potentially several migrations in the same 'package main' --- cmd_create.go | 7 +- db-sample/migrations/003_and_again.go | 15 --- .../migrations/20130106222315_and_again.go | 15 +++ migration_go.go | 112 ++---------------- util.go | 17 +++ 5 files changed, 48 insertions(+), 118 deletions(-) delete mode 100644 db-sample/migrations/003_and_again.go create mode 100644 db-sample/migrations/20130106222315_and_again.go diff --git a/cmd_create.go b/cmd_create.go index 56335b1..a3cbcd1 100644 --- a/cmd_create.go +++ b/cmd_create.go @@ -64,20 +64,19 @@ func init() { } var goMigrationScaffoldTmpl = template.Must(template.New("driver").Parse(` -package migration_{{ . }} +package main import ( "database/sql" - "fmt" ) // Up is executed when this migration is applied -func Up(txn *sql.Tx) { +func Up_{{ . }}(txn *sql.Tx) { } // Down is executed when this migration is rolled back -func Down(txn *sql.Tx) { +func Down_{{ . }}(txn *sql.Tx) { } `)) diff --git a/db-sample/migrations/003_and_again.go b/db-sample/migrations/003_and_again.go deleted file mode 100644 index 776ae5d..0000000 --- a/db-sample/migrations/003_and_again.go +++ /dev/null @@ -1,15 +0,0 @@ - -package migration_003 - -import ( - "database/sql" - "fmt" -) - -func Up(txn *sql.Tx) { - fmt.Println("Hello from migration_003 Up!") -} - -func Down(txn *sql.Tx) { - fmt.Println("Hello from migration_003 Down!") -} diff --git a/db-sample/migrations/20130106222315_and_again.go b/db-sample/migrations/20130106222315_and_again.go new file mode 100644 index 0000000..1aac8ba --- /dev/null +++ b/db-sample/migrations/20130106222315_and_again.go @@ -0,0 +1,15 @@ + +package main + +import ( + "database/sql" + "fmt" +) + +func Up_20130106222315(txn *sql.Tx) { + fmt.Println("Hello from migration 20130106222315 Up!") +} + +func Down_20130106222315(txn *sql.Tx) { + fmt.Println("Hello from migration 20130106222315 Down!") +} diff --git a/migration_go.go b/migration_go.go index fef6127..5d33729 100644 --- a/migration_go.go +++ b/migration_go.go @@ -1,37 +1,21 @@ package main import ( - "bufio" "fmt" - "io" "io/ioutil" "log" "os" "os/exec" "path/filepath" - "strings" "text/template" ) -const ( - UpDecl = "func Up(txn *sql.Tx) {" - UpDeclFmt = "func migration_%d_Up(txn *sql.Tx) {\n" - DownDecl = "func Down(txn *sql.Tx) {" - DownDeclFmt = "func migration_%d_Down(txn *sql.Tx) {\n" -) - type TemplateData struct { Version int64 DBDriver string DBOpen string - Direction string -} - -func directionStr(direction bool) string { - if direction { - return "Up" - } - return "Down" + Direction bool + Func string } // @@ -50,11 +34,17 @@ func runGoMigration(conf *DBConf, path string, version int64, direction bool) er } defer os.RemoveAll(d) + directionStr := "Down" + if direction { + directionStr = "Up" + } + td := &TemplateData{ Version: version, DBDriver: conf.Driver, DBOpen: conf.OpenStr, - Direction: directionStr(direction), + Direction: direction, + Func: fmt.Sprintf("%v_%v", directionStr, version), } main, e := writeTemplateToFile(filepath.Join(d, "goose_main.go"), goMigrationTmpl, td) if e != nil { @@ -62,7 +52,7 @@ func runGoMigration(conf *DBConf, path string, version int64, direction bool) er } outpath := filepath.Join(d, filepath.Base(path)) - if e = writeSubstituted(path, outpath, version); e != nil { + if _, e = copyFile(outpath, path); e != nil { log.Fatal(e) } @@ -76,82 +66,6 @@ func runGoMigration(conf *DBConf, path string, version int64, direction bool) er return nil } -// -// a little cheesy, but do a simple text substitution on the contents of the -// migration script. this has 2 motivations: -// * rewrite the package to 'main' so that we can run as part of `go run` -// * namespace the Up() and Down() funcs so we can compile several -// .go migrations into the same binary for execution -// -func writeSubstituted(inpath, outpath string, version int64) error { - - fin, e := os.Open(inpath) - if e != nil { - return e - } - defer fin.Close() - - fout, e := os.OpenFile(outpath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666) - if e != nil { - return e - } - defer fout.Close() - - rw := bufio.NewReadWriter(bufio.NewReader(fin), bufio.NewWriter(fout)) - - for { - // XXX: could optimize the case in which we've already found - // everything we're looking for, and just copy the rest in bulk - - line, _, e := rw.ReadLine() - // XXX: handle isPrefix from ReadLine() - - if e != nil { - if e != io.EOF { - log.Fatal("failed to read from migration script:", e) - } - - rw.Flush() - break - } - - lineStr := string(line) - - if strings.HasPrefix(lineStr, "package migration_") { - if _, e := rw.WriteString("package main\n"); e != nil { - return e - } - continue - } - - if lineStr == UpDecl { - up := fmt.Sprintf(UpDeclFmt, version) - if _, e := rw.WriteString(up); e != nil { - return e - } - continue - } - - if lineStr == DownDecl { - down := fmt.Sprintf(DownDeclFmt, version) - if _, e := rw.WriteString(down); e != nil { - return e - } - continue - } - - // default case - if _, e := rw.Write(line); e != nil { - return e - } - if _, e := rw.WriteRune('\n'); e != nil { - return e - } - } - - return nil -} - // // template for the main entry point to a go-based migration. // this gets linked against the substituted versions of the user-supplied @@ -179,11 +93,11 @@ func main() { log.Fatal("db.Begin:", err) } - migration_{{ .Version }}_{{ .Direction }}(txn) + {{ .Func }}(txn) // XXX: drop goose_db_version table on some minimum version number? - isApplied := "{{ .Direction }}" == "Up" - versionStmt := fmt.Sprintf("INSERT INTO goose_db_version (version_id, is_applied) VALUES (%d, %t);", {{ .Version }}, isApplied) + versionFmt := "INSERT INTO goose_db_version (version_id, is_applied) VALUES (%v, %t);" + versionStmt := fmt.Sprintf(versionFmt, int64({{ .Version }}), {{ .Direction }}) if _, err = txn.Exec(versionStmt); err != nil { txn.Rollback() log.Fatal("failed to write version: ", err) diff --git a/util.go b/util.go index 591eb64..4bb74af 100644 --- a/util.go +++ b/util.go @@ -1,6 +1,7 @@ package main import ( + "io" "os" "text/template" ) @@ -21,3 +22,19 @@ func writeTemplateToFile(path string, t *template.Template, data interface{}) (s return f.Name(), nil } + +func copyFile(dst, src string) (int64, error) { + sf, err := os.Open(src) + if err != nil { + return 0, err + } + defer sf.Close() + + df, err := os.Create(dst) + if err != nil { + return 0, err + } + defer df.Close() + + return io.Copy(df, sf) +}