// Reset runs the down and up migration function func Reset(pipe chan interface{}, url, migrationsPath string) { pipe1 := pipep.New() go Down(pipe1, url, migrationsPath) if ok := pipep.WaitAndRedirect(pipe1, pipe, handleInterrupts()); !ok { go pipep.Close(pipe, nil) return } else { go Up(pipe, url, migrationsPath) } }
// Down rolls back all migrations func Down(pipe chan interface{}, url, migrationsPath string) { d, files, version, err := initDriverAndReadMigrationFilesAndGetVersion(url, migrationsPath) if err != nil { go pipep.Close(pipe, err) return } applyMigrationFiles, err := files.ToFirstFrom(version) if err != nil { if err2 := d.Close(); err2 != nil { pipe <- err2 } go pipep.Close(pipe, err) return } if len(applyMigrationFiles) > 0 { for _, f := range applyMigrationFiles { pipe1 := pipep.New() go d.Migrate(f, pipe1) if ok := pipep.WaitAndRedirect(pipe1, pipe, handleInterrupts()); !ok { break } } if err2 := d.Close(); err2 != nil { pipe <- err2 } go pipep.Close(pipe, nil) return } else { if err2 := d.Close(); err2 != nil { pipe <- err2 } go pipep.Close(pipe, nil) return } }
func TestMigrate(t *testing.T) { var session *gocql.Session host := os.Getenv("CASSANDRA_PORT_9042_TCP_ADDR") port := os.Getenv("CASSANDRA_PORT_9042_TCP_PORT") driverUrl := "cassandra://" + host + ":" + port + "/system" // prepare a clean test database u, err := url.Parse(driverUrl) if err != nil { t.Fatal(err) } cluster := gocql.NewCluster(u.Host) cluster.Keyspace = u.Path[1:len(u.Path)] cluster.Consistency = gocql.All cluster.Timeout = 1 * time.Minute session, err = cluster.CreateSession() if err != nil { t.Fatal(err) } if err := session.Query(`CREATE KEYSPACE IF NOT EXISTS migrate WITH REPLICATION = {'class': 'SimpleStrategy', 'replication_factor': 1};`).Exec(); err != nil { t.Fatal(err) } cluster.Keyspace = "migrate" session, err = cluster.CreateSession() driverUrl = "cassandra://" + host + ":" + port + "/migrate" d := &Driver{} if err := d.Initialize(driverUrl); err != nil { t.Fatal(err) } files := []file.File{ { Path: "/foobar", FileName: "001_foobar.up.sql", Version: 1, Name: "foobar", Direction: direction.Up, Content: []byte(` CREATE TABLE yolo ( id varint primary key, msg text ); CREATE INDEX ON yolo (msg); `), }, { Path: "/foobar", FileName: "002_foobar.down.sql", Version: 1, Name: "foobar", Direction: direction.Down, Content: []byte(` DROP TABLE yolo; `), }, { Path: "/foobar", FileName: "002_foobar.up.sql", Version: 2, Name: "foobar", Direction: direction.Up, Content: []byte(` CREATE TABLE error ( id THIS WILL CAUSE AN ERROR ) `), }, } pipe := pipep.New() go d.Migrate(files[0], pipe) errs := pipep.ReadErrors(pipe) if len(errs) > 0 { t.Fatal(errs) } pipe = pipep.New() go d.Migrate(files[1], pipe) errs = pipep.ReadErrors(pipe) if len(errs) > 0 { t.Fatal(errs) } pipe = pipep.New() go d.Migrate(files[2], pipe) errs = pipep.ReadErrors(pipe) if len(errs) == 0 { t.Error("Expected test case to fail") } if err := d.Close(); err != nil { t.Fatal(err) } }
func main() { flag.Usage = func() { helpCmd() } flag.Parse() command := flag.Arg(0) if *version { fmt.Println(Version) os.Exit(0) } if *migrationsPath == "" { *migrationsPath, _ = os.Getwd() } switch command { case "create": verifyMigrationsPath(*migrationsPath) name := flag.Arg(1) if name == "" { fmt.Println("Please specify name.") os.Exit(1) } migrationFile, err := migrate.Create(*url, *migrationsPath, name) if err != nil { fmt.Println(err) os.Exit(1) } fmt.Printf("Version %v migration files created in %v:\n", migrationFile.Version, *migrationsPath) fmt.Println(migrationFile.UpFile.FileName) fmt.Println(migrationFile.DownFile.FileName) case "migrate": verifyMigrationsPath(*migrationsPath) relativeN := flag.Arg(1) relativeNInt, err := strconv.Atoi(relativeN) if err != nil { fmt.Println("Unable to parse param <n>.") os.Exit(1) } timerStart = time.Now() pipe := pipep.New() go migrate.Migrate(pipe, *url, *migrationsPath, relativeNInt) ok := writePipe(pipe) printTimer() if !ok { os.Exit(1) } case "goto": verifyMigrationsPath(*migrationsPath) toVersion := flag.Arg(1) toVersionInt, err := strconv.Atoi(toVersion) if err != nil || toVersionInt < 0 { fmt.Println("Unable to parse param <v>.") os.Exit(1) } currentVersion, err := migrate.Version(*url, *migrationsPath) if err != nil { fmt.Println(err) os.Exit(1) } relativeNInt := toVersionInt - int(currentVersion) timerStart = time.Now() pipe := pipep.New() go migrate.Migrate(pipe, *url, *migrationsPath, relativeNInt) ok := writePipe(pipe) printTimer() if !ok { os.Exit(1) } case "up": verifyMigrationsPath(*migrationsPath) timerStart = time.Now() pipe := pipep.New() go migrate.Up(pipe, *url, *migrationsPath) ok := writePipe(pipe) printTimer() if !ok { os.Exit(1) } case "down": verifyMigrationsPath(*migrationsPath) timerStart = time.Now() pipe := pipep.New() go migrate.Down(pipe, *url, *migrationsPath) ok := writePipe(pipe) printTimer() if !ok { os.Exit(1) } case "redo": verifyMigrationsPath(*migrationsPath) timerStart = time.Now() pipe := pipep.New() go migrate.Redo(pipe, *url, *migrationsPath) ok := writePipe(pipe) printTimer() if !ok { os.Exit(1) } case "reset": verifyMigrationsPath(*migrationsPath) timerStart = time.Now() pipe := pipep.New() go migrate.Reset(pipe, *url, *migrationsPath) ok := writePipe(pipe) printTimer() if !ok { os.Exit(1) } case "version": verifyMigrationsPath(*migrationsPath) version, err := migrate.Version(*url, *migrationsPath) if err != nil { fmt.Println(err) os.Exit(1) } fmt.Println(version) default: fallthrough case "help": helpCmd() } }
// TestMigrate runs some additional tests on Migrate(). // Basic testing is already done in migrate/migrate_test.go func TestMigrate(t *testing.T) { host := os.Getenv("MYSQL_PORT_3306_TCP_ADDR") port := os.Getenv("MYSQL_PORT_3306_TCP_PORT") driverUrl := "mysql://root@tcp(" + host + ":" + port + ")/migratetest" // prepare clean database connection, err := sql.Open("mysql", strings.SplitN(driverUrl, "mysql://", 2)[1]) if err != nil { t.Fatal(err) } if _, err := connection.Exec(`DROP TABLE IF EXISTS yolo, yolo1, ` + tableName); err != nil { t.Fatal(err) } d := &Driver{} if err := d.Initialize(driverUrl); err != nil { t.Fatal(err) } files := []file.File{ { Path: "/foobar", FileName: "001_foobar.up.sql", Version: 1, Name: "foobar", Direction: direction.Up, Content: []byte(` CREATE TABLE yolo ( id int(11) not null primary key auto_increment ); CREATE TABLE yolo1 ( id int(11) not null primary key auto_increment ); `), }, { Path: "/foobar", FileName: "002_foobar.down.sql", Version: 1, Name: "foobar", Direction: direction.Down, Content: []byte(` DROP TABLE yolo; `), }, { Path: "/foobar", FileName: "002_foobar.up.sql", Version: 2, Name: "foobar", Direction: direction.Up, Content: []byte(` // a comment CREATE TABLE error ( id THIS WILL CAUSE AN ERROR ); `), }, } pipe := pipep.New() go d.Migrate(files[0], pipe) errs := pipep.ReadErrors(pipe) if len(errs) > 0 { t.Fatal(errs) } pipe = pipep.New() go d.Migrate(files[1], pipe) errs = pipep.ReadErrors(pipe) if len(errs) > 0 { t.Fatal(errs) } pipe = pipep.New() go d.Migrate(files[2], pipe) errs = pipep.ReadErrors(pipe) if len(errs) == 0 { t.Error("Expected test case to fail") } if err := d.Close(); err != nil { t.Fatal(err) } }
// TestMigrate runs some additional tests on Migrate(). // Basic testing is already done in migrate/migrate_test.go func TestMigrate(t *testing.T) { host := os.Getenv("POSTGRES_PORT_5432_TCP_ADDR") port := os.Getenv("POSTGRES_PORT_5432_TCP_PORT") driverUrl := "postgres://postgres@" + host + ":" + port + "/template1?sslmode=disable" // prepare clean database connection, err := sql.Open("postgres", driverUrl) if err != nil { t.Fatal(err) } if _, err := connection.Exec(` DROP TABLE IF EXISTS yolo; DROP TABLE IF EXISTS ` + tableName + `;`); err != nil { t.Fatal(err) } d := &Driver{} if err := d.Initialize(driverUrl); err != nil { t.Fatal(err) } files := []file.File{ { Path: "/foobar", FileName: "001_foobar.up.sql", Version: 1, Name: "foobar", Direction: direction.Up, Content: []byte(` CREATE TABLE yolo ( id serial not null primary key ); `), }, { Path: "/foobar", FileName: "002_foobar.down.sql", Version: 1, Name: "foobar", Direction: direction.Down, Content: []byte(` DROP TABLE yolo; `), }, { Path: "/foobar", FileName: "002_foobar.up.sql", Version: 2, Name: "foobar", Direction: direction.Up, Content: []byte(` CREATE TABLE error ( id THIS WILL CAUSE AN ERROR ) `), }, } pipe := pipep.New() go d.Migrate(files[0], pipe) errs := pipep.ReadErrors(pipe) if len(errs) > 0 { t.Fatal(errs) } pipe = pipep.New() go d.Migrate(files[1], pipe) errs = pipep.ReadErrors(pipe) if len(errs) > 0 { t.Fatal(errs) } pipe = pipep.New() go d.Migrate(files[2], pipe) errs = pipep.ReadErrors(pipe) if len(errs) == 0 { t.Error("Expected test case to fail") } if err := d.Close(); err != nil { t.Fatal(err) } }
// UpSync is synchronous version of Up func UpSync(url, migrationsPath string) (err []error, ok bool) { pipe := pipep.New() go Up(pipe, url, migrationsPath) err = pipep.ReadErrors(pipe) return err, len(err) == 0 }
// NewPipe is a convenience function for pipe.New(). // This is helpful if the user just wants to import this package and nothing else. func NewPipe() chan interface{} { return pipep.New() }
// MigrateSync is synchronous version of Migrate func MigrateSync(url, migrationsPath string, relativeN int) (err []error, ok bool) { pipe := pipep.New() go Migrate(pipe, url, migrationsPath, relativeN) err = pipep.ReadErrors(pipe) return err, len(err) == 0 }