mirror of https://github.com/pressly/goose.git
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'
parent
85a0c8b565
commit
f89880da03
|
@ -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) {
|
||||
|
||||
}
|
||||
`))
|
||||
|
|
|
@ -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!")
|
||||
}
|
|
@ -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!")
|
||||
}
|
112
migration_go.go
112
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)
|
||||
|
|
17
util.go
17
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)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue