From 8496f2a8a2ce5746adbc91250e7b7012cf923291 Mon Sep 17 00:00:00 2001 From: mattes Date: Tue, 16 Sep 2014 18:04:50 +0200 Subject: [PATCH] initial --- driver/mysql/README.md | 20 ++++++ driver/mysql/mysql.go | 121 +++++++++++++++++++++++++++++++++++++ driver/mysql/mysql_test.go | 93 ++++++++++++++++++++++++++++ 3 files changed, 234 insertions(+) create mode 100644 driver/mysql/README.md create mode 100644 driver/mysql/mysql.go create mode 100644 driver/mysql/mysql_test.go diff --git a/driver/mysql/README.md b/driver/mysql/README.md new file mode 100644 index 0000000..7cf20a5 --- /dev/null +++ b/driver/mysql/README.md @@ -0,0 +1,20 @@ +# MySQL Driver + +* Runs migrations in transcations. + That means that if a migration failes, it will be safely rolled back. +* Tries to return helpful error messages. +* Stores migration version details in table ``schema_migrations``. + This table will be auto-generated. + + +## Usage + +```bash +migrate -url postgres://user@host:port/database -path ./db/migrations create add_field_to_table +migrate -url postgres://user@host:port/database -path ./db/migrations up +migrate help # for more info +``` + +## Authors + +* Matthias Kadenbach, https://github.com/mattes \ No newline at end of file diff --git a/driver/mysql/mysql.go b/driver/mysql/mysql.go new file mode 100644 index 0000000..dd977fe --- /dev/null +++ b/driver/mysql/mysql.go @@ -0,0 +1,121 @@ +// Package postgres implements the Driver interface. +package postgres + +import ( + "database/sql" + "errors" + "fmt" + "github.com/lib/pq" + "github.com/mattes/migrate/file" + "github.com/mattes/migrate/migrate/direction" + "strconv" +) + +type Driver struct { + db *sql.DB +} + +const tableName = "schema_migrations" + +func (driver *Driver) Initialize(url string) error { + db, err := sql.Open("postgres", 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) ensureVersionTableExists() error { + if _, err := driver.db.Exec("CREATE TABLE IF NOT EXISTS " + tableName + " (version int not null primary key);"); err != nil { + return err + } + return nil +} + +func (driver *Driver) FilenameExtension() string { + return "sql" +} + +func (driver *Driver) Migrate(f file.File, pipe chan interface{}) { + defer close(pipe) + pipe <- f + + tx, err := driver.db.Begin() + if err != nil { + pipe <- err + return + } + + if f.Direction == direction.Up { + if _, err := tx.Exec("INSERT INTO "+tableName+" (version) VALUES ($1)", f.Version); err != nil { + pipe <- err + if err := tx.Rollback(); err != nil { + pipe <- err + } + return + } + } else if f.Direction == direction.Down { + if _, err := tx.Exec("DELETE FROM "+tableName+" WHERE version=$1", f.Version); err != nil { + pipe <- err + if err := tx.Rollback(); err != nil { + pipe <- err + } + return + } + } + + if err := f.ReadContent(); err != nil { + pipe <- err + return + } + + if _, err := tx.Exec(string(f.Content)); err != nil { + pqErr := err.(*pq.Error) + offset, err := strconv.Atoi(pqErr.Position) + if err == nil && offset >= 0 { + lineNo, columnNo := file.LineColumnFromOffset(f.Content, offset-1) + errorPart := file.LinesBeforeAndAfter(f.Content, lineNo, 5, 5, true) + pipe <- errors.New(fmt.Sprintf("%s %v: %s in line %v, column %v:\n\n%s", pqErr.Severity, pqErr.Code, pqErr.Message, lineNo, columnNo, string(errorPart))) + } else { + pipe <- errors.New(fmt.Sprintf("%s %v: %s", pqErr.Severity, pqErr.Code, pqErr.Message)) + } + + if err := tx.Rollback(); err != nil { + pipe <- err + } + return + } + + if err := tx.Commit(); err != nil { + pipe <- err + return + } +} + +func (driver *Driver) Version() (uint64, error) { + var version uint64 + err := driver.db.QueryRow("SELECT version FROM " + tableName + " ORDER BY version DESC").Scan(&version) + switch { + case err == sql.ErrNoRows: + return 0, nil + case err != nil: + return 0, err + default: + return version, nil + } +} diff --git a/driver/mysql/mysql_test.go b/driver/mysql/mysql_test.go new file mode 100644 index 0000000..2a981d0 --- /dev/null +++ b/driver/mysql/mysql_test.go @@ -0,0 +1,93 @@ +package postgres + +import ( + "database/sql" + "github.com/mattes/migrate/file" + "github.com/mattes/migrate/migrate/direction" + pipep "github.com/mattes/migrate/pipe" + "testing" +) + +// TestMigrate runs some additional tests on Migrate(). +// Basic testing is already done in migrate/migrate_test.go +func TestMigrate(t *testing.T) { + driverUrl := "postgres://localhost/migratetest?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) + } +}