From f8632df5904e01b20cf4e8adb083d0ca27e9fcf1 Mon Sep 17 00:00:00 2001 From: Vojtech Vitek Date: Wed, 6 Mar 2019 01:03:25 -0500 Subject: [PATCH 1/4] There's no need to test upstream function --- db_test.go | 54 ------------------------------------------------------ 1 file changed, 54 deletions(-) delete mode 100644 db_test.go diff --git a/db_test.go b/db_test.go deleted file mode 100644 index dfc52b5..0000000 --- a/db_test.go +++ /dev/null @@ -1,54 +0,0 @@ -package goose - -import ( - "testing" -) - -func TestNormalizeMySQLDSN(t *testing.T) { - t.Parallel() - - testCases := []struct { - desc string - in string - out string - expectedErr string - }{ - { - desc: "errors if dsn is invalid", - in: "root:password@tcp(mysql:3306)", // forgot the database name - expectedErr: "invalid DSN: missing the slash separating the database name", - }, - { - desc: "works when there are no query parameters supplied with the dsn", - in: "root:password@tcp(mysql:3306)/db", - out: "root:password@tcp(mysql:3306)/db?parseTime=true", - }, - { - desc: "works when parseTime is already set to true supplied with the dsn", - in: "root:password@tcp(mysql:3306)/db?parseTime=true", - out: "root:password@tcp(mysql:3306)/db?parseTime=true", - }, - { - desc: "persists other parameters if they are present", - in: "root:password@tcp(mysql:3306)/db?allowCleartextPasswords=true&interpolateParams=true", - out: "root:password@tcp(mysql:3306)/db?allowCleartextPasswords=true&interpolateParams=true&parseTime=true", - }, - } - - for _, tc := range testCases { - t.Run(tc.desc, func(t *testing.T) { - out, err := normalizeMySQLDSN(tc.in) - if tc.expectedErr != "" { - if err == nil { - t.Errorf("expected an error, but did not have one, had (%#v, %#v)", out, err) - } else if err.Error() != tc.expectedErr { - t.Errorf("expected error %s but had %s", tc.expectedErr, err.Error()) - } - } else if err != nil { - t.Errorf("had unexpected error %s", err.Error()) - } else if out != tc.out { - t.Errorf("had output mismatch, wanted %s but had %s", tc.out, out) - } - }) - } -} From 0cdcc4512258097e6c7b0cbb1fbdfeddb8ee28f0 Mon Sep 17 00:00:00 2001 From: Vojtech Vitek Date: Wed, 6 Mar 2019 01:06:31 -0500 Subject: [PATCH 2/4] Move the MySQL normalize DNS function dependency to cmd/ --- cmd/goose/driver_mysql.go | 27 ++++++++++++++++++++++++++- cmd/goose/driver_no_mysql.go | 12 ++++++++++++ cmd/goose/main.go | 8 +------- db.go | 23 +---------------------- 4 files changed, 40 insertions(+), 30 deletions(-) create mode 100644 cmd/goose/driver_no_mysql.go diff --git a/cmd/goose/driver_mysql.go b/cmd/goose/driver_mysql.go index f916732..d5071f8 100644 --- a/cmd/goose/driver_mysql.go +++ b/cmd/goose/driver_mysql.go @@ -1,8 +1,33 @@ -// +build !no_mysql +// +build mysql package main import ( + "log" + + "github.com/go-sql-driver/mysql" + _ "github.com/go-sql-driver/mysql" _ "github.com/ziutek/mymysql/godrv" ) + +// normalizeMySQLDSN parses the dsn used with the mysql driver to always have +// the parameter `parseTime` set to true. This allows internal goose logic +// to assume that DATETIME/DATE/TIMESTAMP can be scanned into the time.Time +// type. +func normalizeDBString(str string) string { + var err error + str, err = normalizeMySQLDSN(dns string) + if err != nil { + log.Fatalf("failed to normalize MySQL connection string: %v", err) + } +} + +func normalizeMySQLDSN(dns string) (string, error) { + config, err := mysql.ParseDSN(dsn) + if err != nil { + return "", err + } + config.ParseTime = true + return config.FormatDSN(), nil +} diff --git a/cmd/goose/driver_no_mysql.go b/cmd/goose/driver_no_mysql.go new file mode 100644 index 0000000..2ed6346 --- /dev/null +++ b/cmd/goose/driver_no_mysql.go @@ -0,0 +1,12 @@ +// +build !mysql + +package main + +import ( + _ "github.com/go-sql-driver/mysql" + _ "github.com/ziutek/mymysql/godrv" +) + +func normalizeDBString(str string) string { + return str +} diff --git a/cmd/goose/main.go b/cmd/goose/main.go index bd9469b..48f07bd 100644 --- a/cmd/goose/main.go +++ b/cmd/goose/main.go @@ -55,13 +55,7 @@ func main() { driver, dbstring, command := args[0], args[1], args[2] - switch dbstring { - case "": - log.Fatalf("-dbstring=%q not supported\n", dbstring) - default: - } - - db, err := goose.OpenDBWithDriver(driver, dbstring) + db, err := goose.OpenDBWithDriver(driver, normalizeDBString(dbstring)) if err != nil { log.Fatalf("-dbstring=%q: %v\n", dbstring, err) } diff --git a/db.go b/db.go index c4f03c6..b61f886 100644 --- a/db.go +++ b/db.go @@ -3,8 +3,6 @@ package goose import ( "database/sql" "fmt" - - "github.com/go-sql-driver/mysql" ) // OpenDBWithDriver creates a connection a database, and modifies goose @@ -22,28 +20,9 @@ func OpenDBWithDriver(driver string, dbstring string) (*sql.DB, error) { } switch driver { - case "postgres", "sqlite3": + case "postgres", "sqlite3", "mysql": return sql.Open(driver, dbstring) - case "mysql": - dsn, err := normalizeMySQLDSN(dbstring) - if err != nil { - return nil, err - } - return sql.Open(driver, dsn) default: return nil, fmt.Errorf("unsupported driver %s", driver) } } - -// normalizeMySQLDSN parses the dsn used with the mysql driver to always have -// the parameter `parseTime` set to true. This allows internal goose logic -// to assume that DATETIME/DATE/TIMESTAMP can be scanned into the time.Time -// type. -func normalizeMySQLDSN(dsn string) (string, error) { - config, err := mysql.ParseDSN(dsn) - if err != nil { - return "", err - } - config.ParseTime = true - return config.FormatDSN(), nil -} From 5cd6f8c9d0be16a22652729bcf576e5241bc5db7 Mon Sep 17 00:00:00 2001 From: Vojtech Vitek Date: Wed, 6 Mar 2019 01:20:06 -0500 Subject: [PATCH 3/4] Make this all work --- README.md | 2 +- cmd/goose/driver_mysql.go | 29 ++++++++++--------- cmd/goose/driver_no_mysql.go | 4 +-- .../{driver_psql.go => driver_postgres.go} | 2 +- .../{driver_sqlite.go => driver_sqlite3.go} | 2 +- cmd/goose/main.go | 2 +- goose_test.go | 10 +++---- 7 files changed, 27 insertions(+), 24 deletions(-) rename cmd/goose/{driver_psql.go => driver_postgres.go} (68%) rename cmd/goose/{driver_sqlite.go => driver_sqlite3.go} (72%) diff --git a/README.md b/README.md index 0333aae..c802843 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ This will install the `goose` binary to your `$GOPATH/bin` directory. For a lite version of the binary without DB connection dependent commands, use the exclusive build tags: - $ go build -tags='no_mysql no_sqlite no_psql' -i -o goose ./cmd/goose + $ go build -tags='no_postgres no_mysql no_sqlite3' -i -o goose ./cmd/goose # Usage diff --git a/cmd/goose/driver_mysql.go b/cmd/goose/driver_mysql.go index d5071f8..3fa0126 100644 --- a/cmd/goose/driver_mysql.go +++ b/cmd/goose/driver_mysql.go @@ -1,4 +1,4 @@ -// +build mysql +// +build !no_mysql package main @@ -15,19 +15,22 @@ import ( // the parameter `parseTime` set to true. This allows internal goose logic // to assume that DATETIME/DATE/TIMESTAMP can be scanned into the time.Time // type. -func normalizeDBString(str string) string { - var err error - str, err = normalizeMySQLDSN(dns string) - if err != nil { - log.Fatalf("failed to normalize MySQL connection string: %v", err) +func normalizeDBString(driver string, str string) string { + if driver == "mysql" { + var err error + str, err = normalizeMySQLDSN(str) + if err != nil { + log.Fatalf("failed to normalize MySQL connection string: %v", err) + } } + return str } -func normalizeMySQLDSN(dns string) (string, error) { - config, err := mysql.ParseDSN(dsn) - if err != nil { - return "", err - } - config.ParseTime = true - return config.FormatDSN(), nil +func normalizeMySQLDSN(dsn string) (string, error) { + config, err := mysql.ParseDSN(dsn) + if err != nil { + return "", err + } + config.ParseTime = true + return config.FormatDSN(), nil } diff --git a/cmd/goose/driver_no_mysql.go b/cmd/goose/driver_no_mysql.go index 2ed6346..a18ccff 100644 --- a/cmd/goose/driver_no_mysql.go +++ b/cmd/goose/driver_no_mysql.go @@ -1,4 +1,4 @@ -// +build !mysql +// +build no_mysql package main @@ -7,6 +7,6 @@ import ( _ "github.com/ziutek/mymysql/godrv" ) -func normalizeDBString(str string) string { +func normalizeDBString(driver string, str string) string { return str } diff --git a/cmd/goose/driver_psql.go b/cmd/goose/driver_postgres.go similarity index 68% rename from cmd/goose/driver_psql.go rename to cmd/goose/driver_postgres.go index 1a8e50e..d8f5745 100644 --- a/cmd/goose/driver_psql.go +++ b/cmd/goose/driver_postgres.go @@ -1,4 +1,4 @@ -// +build !no_pq +// +build !no_postgres package main diff --git a/cmd/goose/driver_sqlite.go b/cmd/goose/driver_sqlite3.go similarity index 72% rename from cmd/goose/driver_sqlite.go rename to cmd/goose/driver_sqlite3.go index a790a19..37a4eee 100644 --- a/cmd/goose/driver_sqlite.go +++ b/cmd/goose/driver_sqlite3.go @@ -1,4 +1,4 @@ -// +build !no_sqlite +// +build !no_sqlite3 package main diff --git a/cmd/goose/main.go b/cmd/goose/main.go index 48f07bd..f6e6707 100644 --- a/cmd/goose/main.go +++ b/cmd/goose/main.go @@ -55,7 +55,7 @@ func main() { driver, dbstring, command := args[0], args[1], args[2] - db, err := goose.OpenDBWithDriver(driver, normalizeDBString(dbstring)) + db, err := goose.OpenDBWithDriver(driver, normalizeDBString(driver, dbstring)) if err != nil { log.Fatalf("-dbstring=%q: %v\n", dbstring, err) } diff --git a/goose_test.go b/goose_test.go index e219aa6..b453880 100644 --- a/goose_test.go +++ b/goose_test.go @@ -47,20 +47,20 @@ func TestLiteBinary(t *testing.T) { t.Fatal(err) } - defer os.RemoveAll(dir) // clean up - defer os.Remove("./bin/light-goose") // clean up + defer os.RemoveAll(dir) // clean up + defer os.Remove("./bin/lite-goose") // clean up // this has to be done outside of the loop // since go only supports space separated tags list. - cmd := exec.Command("go", "build", "-tags='no_mysql no_sqlite no_psql'", "-o", "./bin/light-goose", "./cmd/goose") + cmd := exec.Command("go", "build", "-tags='no_postgres no_mysql no_sqlite3'", "-o", "./bin/lite-goose", "./cmd/goose") out, err := cmd.CombinedOutput() if err != nil { t.Fatalf("%s:\n%v\n\n%s", err, cmd, out) } commands := []string{ - fmt.Sprintf("./bin/light-goose -dir=%s create user_indices sql", dir), - fmt.Sprintf("./bin/light-goose -dir=%s fix", dir), + fmt.Sprintf("./bin/lite-goose -dir=%s create user_indices sql", dir), + fmt.Sprintf("./bin/lite-goose -dir=%s fix", dir), } for _, cmd := range commands { From 64cf477c975d582677afce96406adc096a11eb0f Mon Sep 17 00:00:00 2001 From: Vojtech Vitek Date: Wed, 6 Mar 2019 01:29:50 -0500 Subject: [PATCH 4/4] Simplify custom custom binary in examples; Remove duplicated code Fixes #98 --- examples/go-migrations/main.go | 91 +++------------------------------- 1 file changed, 8 insertions(+), 83 deletions(-) diff --git a/examples/go-migrations/main.go b/examples/go-migrations/main.go index ce1f26b..77c9997 100644 --- a/examples/go-migrations/main.go +++ b/examples/go-migrations/main.go @@ -1,3 +1,5 @@ +// This is custom goose binary with sqlite3 support only. + package main import ( @@ -7,11 +9,7 @@ import ( "github.com/pressly/goose" - // Init DB drivers. - _ "github.com/go-sql-driver/mysql" - _ "github.com/lib/pq" _ "github.com/mattn/go-sqlite3" - _ "github.com/ziutek/mymysql/godrv" ) var ( @@ -20,49 +18,19 @@ var ( ) func main() { - flags.Usage = usage flags.Parse(os.Args[1:]) - args := flags.Args() - if len(args) == 0 || args[0] == "-h" || args[0] == "--help" { + + if len(args) < 2 { flags.Usage() return } - switch args[0] { - case "create": - if err := goose.Run("create", nil, *dir, args[1:]...); err != nil { - log.Fatalf("goose run: %v", err) - } - return - case "fix": - if err := goose.Run("fix", nil, *dir); err != nil { - log.Fatalf("goose run: %v", err) - } - return - } + dbstring, command := args[1], args[2] - if len(args) < 3 { - flags.Usage() - return - } - - if args[0] == "-h" || args[0] == "--help" { - flags.Usage() - return - } - - driver, dbstring, command := args[0], args[1], args[2] - - switch dbstring { - case "": - log.Fatalf("-dbstring=%q not supported\n", dbstring) - default: - } - - db, err := goose.OpenDBWithDriver(driver, dbstring) + db, err := goose.OpenDBWithDriver("sqlite3", dbstring) if err != nil { - log.Fatalf("goose run: %v\n", err) + log.Fatalf("goose: failed to open DB: %v\n", err) } arguments := []string{} @@ -71,49 +39,6 @@ func main() { } if err := goose.Run(command, db, *dir, arguments...); err != nil { - log.Fatalf("goose run: %v", err) + log.Fatalf("goose %v: %v", command, err) } } - -func usage() { - log.Print(usagePrefix) - flags.PrintDefaults() - log.Print(usageCommands) -} - -var ( - usagePrefix = `Usage: goose [OPTIONS] DRIVER DBSTRING COMMAND - -Drivers: - postgres - mysql - sqlite3 - redshift - -Examples: - goose sqlite3 ./foo.db status - goose sqlite3 ./foo.db create init sql - goose sqlite3 ./foo.db create add_some_column sql - goose sqlite3 ./foo.db create fetch_user_data go - goose sqlite3 ./foo.db up - - goose postgres "user=postgres dbname=postgres sslmode=disable" status - goose mysql "user:password@/dbname?parseTime=true" status - goose redshift "postgres://user:password@qwerty.us-east-1.redshift.amazonaws.com:5439/db" status - -Options: -` - - usageCommands = ` -Commands: - up Migrate the DB to the most recent version available - up-to VERSION Migrate the DB to a specific VERSION - down Roll back the version by 1 - down-to VERSION Roll back to a specific VERSION - redo Re-run the latest migration - status Dump the migration status for the current DB - version Print the current version of the database - create NAME [sql|go] Creates new migration file with the current timestamp - fix Apply sequential ordering to migrations -` -)