diff --git a/docker-compose.yml b/docker-compose.yml index cac3abcf1..2b13d8f52 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -10,6 +10,7 @@ go-test: - postgres - mysql - cassandra + - crate go-build: <<: *go command: sh -c 'go get -v && go build -ldflags ''-s'' -o migrater' @@ -21,6 +22,8 @@ mysql: image: mysql environment: MYSQL_DATABASE: migratetest - MYSQL_ALLOW_EMPTY_PASSWORD: yes + MYSQL_ALLOW_EMPTY_PASSWORD: "yes" cassandra: image: cassandra:2.2 +crate: + image: crate diff --git a/driver/crate/crate.go b/driver/crate/crate.go new file mode 100644 index 000000000..8773b665d --- /dev/null +++ b/driver/crate/crate.go @@ -0,0 +1,105 @@ +// Package crate implements a driver for the Crate.io database +package crate + +import ( + "database/sql" + "fmt" + "strings" + + _ "github.com/herenow/go-crate" + "github.com/mattes/migrate/driver" + "github.com/mattes/migrate/file" + "github.com/mattes/migrate/migrate/direction" +) + +func init() { + driver.RegisterDriver("crate", &Driver{}) +} + +type Driver struct { + db *sql.DB +} + +const tableName = "schema_migrations" + +func (driver *Driver) Initialize(url string) error { + db, err := sql.Open("crate", url) + if err != nil { + return err + } + if err := db.Ping(); err != nil { + return err + } + driver.db = db + + if err := driver.ensureVersionTableExists(); err != nil { + return err + } + return nil +} + +func (driver *Driver) Close() error { + if err := driver.db.Close(); err != nil { + return err + } + return nil +} + +func (driver *Driver) FilenameExtension() string { + return "sql" +} + +func (driver *Driver) Version() (uint64, error) { + var version uint64 + err := driver.db.QueryRow("SELECT version FROM " + tableName + " ORDER BY version DESC LIMIT 1").Scan(&version) + switch { + case err == sql.ErrNoRows: + return 0, nil + case err != nil: + return 0, err + default: + return version, nil + } +} + +func (driver *Driver) Migrate(f file.File, pipe chan interface{}) { + defer close(pipe) + pipe <- f + + if err := f.ReadContent(); err != nil { + pipe <- err + return + } + + lines := strings.Split(string(f.Content), ";") + for _, line := range lines { + query := strings.TrimSpace(line) + query = strings.Replace(query, ";", "", -1) + if query != "" { + _, err := driver.db.Exec(query) + if err != nil { + pipe <- err + return + } + } + } + + if f.Direction == direction.Up { + if _, err := driver.db.Exec("INSERT INTO "+tableName+" (version) VALUES (?)", f.Version); err != nil { + pipe <- err + return + } + } else if f.Direction == direction.Down { + if _, err := driver.db.Exec("DELETE FROM "+tableName+" WHERE version=?", f.Version); err != nil { + pipe <- err + return + } + } +} + +func (driver *Driver) ensureVersionTableExists() error { + if _, err := driver.db.Exec(fmt.Sprintf("CREATE TABLE IF NOT EXISTS %s (version INTEGER PRIMARY KEY)", tableName)); err != nil { + return err + } + return nil +} diff --git a/driver/crate/crate_test.go b/driver/crate/crate_test.go new file mode 100644 index 000000000..05367ddcf --- /dev/null +++ b/driver/crate/crate_test.go @@ -0,0 +1,87 @@ +package crate + +import ( + "fmt" + "os" + "testing" + + "github.com/mattes/migrate/file" + "github.com/mattes/migrate/migrate/direction" + pipep "github.com/mattes/migrate/pipe" +) + +func TestMigrate(t *testing.T) { + host := os.Getenv("CRATE_PORT_4200_TCP_ADDR") + port := os.Getenv("CRATE_PORT_4200_TCP_PORT") + + url := fmt.Sprintf("http://%s:%s", host, port) + + driver := &Driver{} + + if err := driver.Initialize(url); err != nil { + t.Fatal(err) + } + + successFiles := []file.File{ + { + Path: "/foobar", + FileName: "001_foobar.up.sql", + Version: 1, + Name: "foobar", + Direction: direction.Up, + Content: []byte(` + CREATE TABLE yolo ( + id integer primary key, + msg string + ); + `), + }, + { + Path: "/foobar", + FileName: "002_foobar.down.sql", + Version: 1, + Name: "foobar", + Direction: direction.Down, + Content: []byte(` + DROP TABLE yolo; + `), + }, + } + + failFiles := []file.File{ + { + 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 + ) + `), + }, + } + + for _, file := range successFiles { + pipe := pipep.New() + go driver.Migrate(file, pipe) + errs := pipep.ReadErrors(pipe) + if len(errs) > 0 { + t.Fatal(errs) + } + } + + for _, file := range failFiles { + pipe := pipep.New() + go driver.Migrate(file, pipe) + errs := pipep.ReadErrors(pipe) + if len(errs) == 0 { + t.Fatal("Migration should have failed but succeeded") + } + } + + if err := driver.Close(); err != nil { + t.Fatal(err) + } +} diff --git a/main.go b/main.go index b6da438f1..49f3e523f 100644 --- a/main.go +++ b/main.go @@ -13,6 +13,7 @@ import ( "github.com/fatih/color" _ "github.com/mattes/migrate/driver/bash" _ "github.com/mattes/migrate/driver/cassandra" + _ "github.com/mattes/migrate/driver/crate" _ "github.com/mattes/migrate/driver/mysql" _ "github.com/mattes/migrate/driver/postgres" _ "github.com/mattes/migrate/driver/sqlite3"