Separate cmd package for easier importing (#11)

This commit is contained in:
Adrian Macneil 2017-05-04 20:58:23 -07:00 committed by GitHub
parent 247d7296f8
commit e393f387b3
21 changed files with 408 additions and 368 deletions

1
.agignore Normal file
View file

@ -0,0 +1 @@
/vendor

3
.gitignore vendored
View file

@ -1,3 +1,2 @@
/dist
.DS_Store .DS_Store
dbmate
dist

View file

@ -1,4 +1,4 @@
FROM golang:1.8.0 FROM golang:1.8
# required to force cgo (for sqlite driver) with cross compile # required to force cgo (for sqlite driver) with cross compile
ENV CGO_ENABLED 1 ENV CGO_ENABLED 1
@ -19,6 +19,6 @@ COPY . $GOPATH/src/github.com/amacneil/dbmate
WORKDIR $GOPATH/src/github.com/amacneil/dbmate WORKDIR $GOPATH/src/github.com/amacneil/dbmate
# build # build
RUN go install -v RUN go install -v ./cmd/dbmate
CMD dbmate CMD dbmate

View file

@ -1,7 +1,8 @@
DC := docker-compose DC := docker-compose
BUILD_FLAGS := -ldflags '-s' BUILD_FLAGS := -ldflags '-s'
PACKAGES := . ./cmd/dbmate
all: clean container lint test build all: clean container test lint build
clean: clean:
rm -rf dist rm -rf dist
@ -12,15 +13,15 @@ container:
$(DC) up -d $(DC) up -d
lint: lint:
$(DC) run dbmate golint $(DC) run dbmate golint -set_exit_status $(PACKAGES)
$(DC) run dbmate go vet $(DC) run dbmate go vet $(PACKAGES)
$(DC) run dbmate errcheck $(DC) run dbmate errcheck $(PACKAGES)
test: test:
$(DC) run dbmate go test -v $(DC) run dbmate go test -v $(PACKAGES)
build: clean build: clean
$(DC) run -e GOARCH=386 dbmate go build $(BUILD_FLAGS) -o dist/dbmate-linux-i386 $(DC) run -e GOARCH=386 dbmate go build $(BUILD_FLAGS) -o dist/dbmate-linux-i386 ./cmd/dbmate
$(DC) run -e GOARCH=amd64 dbmate go build $(BUILD_FLAGS) -o dist/dbmate-linux-amd64 $(DC) run -e GOARCH=amd64 dbmate go build $(BUILD_FLAGS) -o dist/dbmate-linux-amd64 ./cmd/dbmate
# musl target does not support sqlite # musl target does not support sqlite
$(DC) run -e GOARCH=amd64 -e CGO_ENABLED=0 dbmate go build $(BUILD_FLAGS) -o dist/dbmate-linux-musl-amd64 $(DC) run -e GOARCH=amd64 -e CGO_ENABLED=0 dbmate go build $(BUILD_FLAGS) -o dist/dbmate-linux-musl-amd64 ./cmd/dbmate

127
cmd/dbmate/main.go Normal file
View file

@ -0,0 +1,127 @@
package main
import (
"fmt"
"log"
"net/url"
"os"
"github.com/amacneil/dbmate"
"github.com/joho/godotenv"
"github.com/urfave/cli"
)
func main() {
loadDotEnv()
app := NewApp()
err := app.Run(os.Args)
if err != nil {
fmt.Fprintf(os.Stderr, "Error: %s\n", err)
os.Exit(1)
}
}
// NewApp creates a new command line app
func NewApp() *cli.App {
app := cli.NewApp()
app.Name = "dbmate"
app.Usage = "A lightweight, framework-independent database migration tool."
app.Version = dbmate.Version
app.Flags = []cli.Flag{
cli.StringFlag{
Name: "migrations-dir, d",
Value: dbmate.DefaultMigrationsDir,
Usage: "specify the directory containing migration files",
},
cli.StringFlag{
Name: "env, e",
Value: "DATABASE_URL",
Usage: "specify an environment variable containing the database URL",
},
}
app.Commands = []cli.Command{
{
Name: "new",
Aliases: []string{"n"},
Usage: "Generate a new migration file",
Action: action(func(db *dbmate.DB, c *cli.Context) error {
name := c.Args().First()
return db.New(name)
}),
},
{
Name: "up",
Usage: "Create database (if necessary) and migrate to the latest version",
Action: action(func(db *dbmate.DB, c *cli.Context) error {
return db.Up()
}),
},
{
Name: "create",
Usage: "Create database",
Action: action(func(db *dbmate.DB, c *cli.Context) error {
return db.Create()
}),
},
{
Name: "drop",
Usage: "Drop database (if it exists)",
Action: action(func(db *dbmate.DB, c *cli.Context) error {
return db.Drop()
}),
},
{
Name: "migrate",
Usage: "Migrate to the latest version",
Action: action(func(db *dbmate.DB, c *cli.Context) error {
return db.Migrate()
}),
},
{
Name: "rollback",
Aliases: []string{"down"},
Usage: "Rollback the most recent migration",
Action: action(func(db *dbmate.DB, c *cli.Context) error {
return db.Rollback()
}),
},
}
return app
}
// load environment variables from .env file
func loadDotEnv() {
if _, err := os.Stat(".env"); err != nil {
return
}
if err := godotenv.Load(); err != nil {
log.Fatal("Error loading .env file")
}
}
// action wraps a cli.ActionFunc with dbmate initialization logic
func action(f func(*dbmate.DB, *cli.Context) error) cli.ActionFunc {
return func(c *cli.Context) error {
u, err := getDatabaseURL(c)
if err != nil {
return err
}
db := dbmate.NewDB(u)
db.MigrationsDir = c.GlobalString("migrations-dir")
return f(db, c)
}
}
// getDatabaseURL returns the current environment database url
func getDatabaseURL(c *cli.Context) (u *url.URL, err error) {
env := c.GlobalString("env")
value := os.Getenv(env)
return url.Parse(value)
}

39
cmd/dbmate/main_test.go Normal file
View file

@ -0,0 +1,39 @@
package main
import (
"flag"
"net/url"
"os"
"testing"
"github.com/stretchr/testify/require"
"github.com/urfave/cli"
)
func testContext(t *testing.T, u *url.URL) *cli.Context {
var err error
err = os.Setenv("DATABASE_URL", u.String())
require.Nil(t, err)
app := NewApp()
flagset := flag.NewFlagSet(app.Name, flag.ContinueOnError)
for _, f := range app.Flags {
f.Apply(flagset)
}
return cli.NewContext(app, flagset, nil)
}
func TestGetDatabaseUrl(t *testing.T) {
envURL, err := url.Parse("foo://example.org/db")
require.Nil(t, err)
ctx := testContext(t, envURL)
u, err := getDatabaseURL(ctx)
require.Nil(t, err)
require.Equal(t, "foo", u.Scheme)
require.Equal(t, "example.org", u.Host)
require.Equal(t, "/db", u.Path)
}

View file

@ -1,169 +0,0 @@
package main
import (
"flag"
"net/url"
"os"
"path/filepath"
"testing"
"github.com/stretchr/testify/require"
"github.com/urfave/cli"
)
var testdataDir string
func testContext(t *testing.T, u *url.URL) *cli.Context {
var err error
// only chdir once, because testdata is relative to current directory
if testdataDir == "" {
testdataDir, err = filepath.Abs("./testdata")
require.Nil(t, err)
err = os.Chdir(testdataDir)
require.Nil(t, err)
}
err = os.Setenv("DATABASE_URL", u.String())
require.Nil(t, err)
app := NewApp()
flagset := flag.NewFlagSet(app.Name, flag.ContinueOnError)
for _, f := range app.Flags {
f.Apply(flagset)
}
return cli.NewContext(app, flagset, nil)
}
func testURLs(t *testing.T) []*url.URL {
return []*url.URL{
postgresTestURL(t),
mySQLTestURL(t),
sqliteTestURL(t),
}
}
func TestGetDatabaseUrl(t *testing.T) {
envURL, err := url.Parse("foo://example.org/db")
require.Nil(t, err)
ctx := testContext(t, envURL)
u, err := GetDatabaseURL(ctx)
require.Nil(t, err)
require.Equal(t, "foo", u.Scheme)
require.Equal(t, "example.org", u.Host)
require.Equal(t, "/db", u.Path)
}
func testMigrateCommandURL(t *testing.T, u *url.URL) {
ctx := testContext(t, u)
// drop and recreate database
err := DropCommand(ctx)
require.Nil(t, err)
err = CreateCommand(ctx)
require.Nil(t, err)
// migrate
err = MigrateCommand(ctx)
require.Nil(t, err)
// verify results
db, err := GetDriverOpen(u)
require.Nil(t, err)
defer mustClose(db)
count := 0
err = db.QueryRow(`select count(*) from schema_migrations
where version = '20151129054053'`).Scan(&count)
require.Nil(t, err)
require.Equal(t, 1, count)
err = db.QueryRow("select count(*) from users").Scan(&count)
require.Nil(t, err)
require.Equal(t, 1, count)
}
func TestMigrateCommand(t *testing.T) {
for _, u := range testURLs(t) {
testMigrateCommandURL(t, u)
}
}
func testUpCommandURL(t *testing.T, u *url.URL) {
ctx := testContext(t, u)
// drop database
err := DropCommand(ctx)
require.Nil(t, err)
// create and migrate
err = UpCommand(ctx)
require.Nil(t, err)
// verify results
db, err := GetDriverOpen(u)
require.Nil(t, err)
defer mustClose(db)
count := 0
err = db.QueryRow(`select count(*) from schema_migrations
where version = '20151129054053'`).Scan(&count)
require.Nil(t, err)
require.Equal(t, 1, count)
err = db.QueryRow("select count(*) from users").Scan(&count)
require.Nil(t, err)
require.Equal(t, 1, count)
}
func TestUpCommand(t *testing.T) {
for _, u := range testURLs(t) {
testUpCommandURL(t, u)
}
}
func testRollbackCommandURL(t *testing.T, u *url.URL) {
ctx := testContext(t, u)
// drop, recreate, and migrate database
err := DropCommand(ctx)
require.Nil(t, err)
err = CreateCommand(ctx)
require.Nil(t, err)
err = MigrateCommand(ctx)
require.Nil(t, err)
// verify migration
db, err := GetDriverOpen(u)
require.Nil(t, err)
defer mustClose(db)
count := 0
err = db.QueryRow(`select count(*) from schema_migrations
where version = '20151129054053'`).Scan(&count)
require.Nil(t, err)
require.Equal(t, 1, count)
// rollback
err = RollbackCommand(ctx)
require.Nil(t, err)
// verify rollback
err = db.QueryRow("select count(*) from schema_migrations").Scan(&count)
require.Nil(t, err)
require.Equal(t, 0, count)
err = db.QueryRow("select count(*) from users").Scan(&count)
require.NotNil(t, err)
require.Regexp(t, "(does not exist|doesn't exist|no such table)", err.Error())
}
func TestRollbackCommand(t *testing.T) {
for _, u := range testURLs(t) {
testRollbackCommandURL(t, u)
}
}

View file

@ -1,4 +1,4 @@
package main package dbmate
import ( import (
"database/sql" "database/sql"
@ -10,18 +10,33 @@ import (
"regexp" "regexp"
"sort" "sort"
"time" "time"
"github.com/urfave/cli"
) )
// UpCommand creates the database (if necessary) and runs migrations // DefaultMigrationsDir specifies default directory to find migration files
func UpCommand(ctx *cli.Context) error { var DefaultMigrationsDir = "./db/migrations"
u, err := GetDatabaseURL(ctx)
if err != nil {
return err
}
drv, err := GetDriver(u.Scheme) // DB allows dbmate actions to be performed on a specified database
type DB struct {
DatabaseURL *url.URL
MigrationsDir string
}
// NewDB initializes a new dbmate database
func NewDB(databaseURL *url.URL) *DB {
return &DB{
DatabaseURL: databaseURL,
MigrationsDir: DefaultMigrationsDir,
}
}
// GetDriver loads the required database driver
func (db *DB) GetDriver() (Driver, error) {
return GetDriver(db.DatabaseURL.Scheme)
}
// Up creates the database (if necessary) and runs migrations
func (db *DB) Up() error {
drv, err := db.GetDriver()
if err != nil { if err != nil {
return err return err
} }
@ -29,71 +44,59 @@ func UpCommand(ctx *cli.Context) error {
// create database if it does not already exist // create database if it does not already exist
// skip this step if we cannot determine status // skip this step if we cannot determine status
// (e.g. user does not have list database permission) // (e.g. user does not have list database permission)
exists, err := drv.DatabaseExists(u) exists, err := drv.DatabaseExists(db.DatabaseURL)
if err == nil && !exists { if err == nil && !exists {
if err := drv.CreateDatabase(u); err != nil { if err := drv.CreateDatabase(db.DatabaseURL); err != nil {
return err return err
} }
} }
// migrate // migrate
return MigrateCommand(ctx) return db.Migrate()
} }
// CreateCommand creates the current database // Create creates the current database
func CreateCommand(ctx *cli.Context) error { func (db *DB) Create() error {
u, err := GetDatabaseURL(ctx) drv, err := db.GetDriver()
if err != nil { if err != nil {
return err return err
} }
drv, err := GetDriver(u.Scheme) return drv.CreateDatabase(db.DatabaseURL)
if err != nil {
return err
}
return drv.CreateDatabase(u)
} }
// DropCommand drops the current database (if it exists) // Drop drops the current database (if it exists)
func DropCommand(ctx *cli.Context) error { func (db *DB) Drop() error {
u, err := GetDatabaseURL(ctx) drv, err := db.GetDriver()
if err != nil { if err != nil {
return err return err
} }
drv, err := GetDriver(u.Scheme) return drv.DropDatabase(db.DatabaseURL)
if err != nil {
return err
}
return drv.DropDatabase(u)
} }
const migrationTemplate = "-- migrate:up\n\n\n-- migrate:down\n\n" const migrationTemplate = "-- migrate:up\n\n\n-- migrate:down\n\n"
// NewCommand creates a new migration file // New creates a new migration file
func NewCommand(ctx *cli.Context) error { func (db *DB) New(name string) error {
// new migration name // new migration name
timestamp := time.Now().UTC().Format("20060102150405") timestamp := time.Now().UTC().Format("20060102150405")
name := ctx.Args().First()
if name == "" { if name == "" {
return fmt.Errorf("Please specify a name for the new migration.") return fmt.Errorf("please specify a name for the new migration")
} }
name = fmt.Sprintf("%s_%s.sql", timestamp, name) name = fmt.Sprintf("%s_%s.sql", timestamp, name)
// create migrations dir if missing // create migrations dir if missing
migrationsDir := ctx.GlobalString("migrations-dir") if err := os.MkdirAll(db.MigrationsDir, 0755); err != nil {
if err := os.MkdirAll(migrationsDir, 0755); err != nil { return fmt.Errorf("unable to create directory `%s`", db.MigrationsDir)
return fmt.Errorf("Unable to create directory `%s`.", migrationsDir)
} }
// check file does not already exist // check file does not already exist
path := filepath.Join(migrationsDir, name) path := filepath.Join(db.MigrationsDir, name)
fmt.Printf("Creating migration: %s\n", path) fmt.Printf("Creating migration: %s\n", path)
if _, err := os.Stat(path); !os.IsNotExist(err) { if _, err := os.Stat(path); !os.IsNotExist(err) {
return fmt.Errorf("File already exists") return fmt.Errorf("file already exists")
} }
// write new migration // write new migration
@ -111,14 +114,6 @@ func NewCommand(ctx *cli.Context) error {
return nil return nil
} }
// GetDatabaseURL returns the current environment database url
func GetDatabaseURL(ctx *cli.Context) (u *url.URL, err error) {
env := ctx.GlobalString("env")
value := os.Getenv(env)
return url.Parse(value)
}
func doTransaction(db *sql.DB, txFunc func(Transaction) error) error { func doTransaction(db *sql.DB, txFunc func(Transaction) error) error {
tx, err := db.Begin() tx, err := db.Begin()
if err != nil { if err != nil {
@ -136,50 +131,44 @@ func doTransaction(db *sql.DB, txFunc func(Transaction) error) error {
return tx.Commit() return tx.Commit()
} }
func openDatabaseForMigration(ctx *cli.Context) (Driver, *sql.DB, error) { func (db *DB) openDatabaseForMigration() (Driver, *sql.DB, error) {
u, err := GetDatabaseURL(ctx) drv, err := db.GetDriver()
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
drv, err := GetDriver(u.Scheme) sqlDB, err := drv.Open(db.DatabaseURL)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
db, err := drv.Open(u) if err := drv.CreateMigrationsTable(sqlDB); err != nil {
if err != nil { mustClose(sqlDB)
return nil, nil, err return nil, nil, err
} }
if err := drv.CreateMigrationsTable(db); err != nil { return drv, sqlDB, nil
mustClose(db)
return nil, nil, err
}
return drv, db, nil
} }
// MigrateCommand migrates database to the latest version // Migrate migrates database to the latest version
func MigrateCommand(ctx *cli.Context) error { func (db *DB) Migrate() error {
migrationsDir := ctx.GlobalString("migrations-dir")
re := regexp.MustCompile(`^\d.*\.sql$`) re := regexp.MustCompile(`^\d.*\.sql$`)
files, err := findMigrationFiles(migrationsDir, re) files, err := findMigrationFiles(db.MigrationsDir, re)
if err != nil { if err != nil {
return err return err
} }
if len(files) == 0 { if len(files) == 0 {
return fmt.Errorf("No migration files found.") return fmt.Errorf("no migration files found")
} }
drv, db, err := openDatabaseForMigration(ctx) drv, sqlDB, err := db.openDatabaseForMigration()
if err != nil { if err != nil {
return err return err
} }
defer mustClose(db) defer mustClose(sqlDB)
applied, err := drv.SelectMigrations(db, -1) applied, err := drv.SelectMigrations(sqlDB, -1)
if err != nil { if err != nil {
return err return err
} }
@ -193,13 +182,13 @@ func MigrateCommand(ctx *cli.Context) error {
fmt.Printf("Applying: %s\n", filename) fmt.Printf("Applying: %s\n", filename)
migration, err := parseMigration(filepath.Join(migrationsDir, filename)) migration, err := parseMigration(filepath.Join(db.MigrationsDir, filename))
if err != nil { if err != nil {
return err return err
} }
// begin transaction // begin transaction
err = doTransaction(db, func(tx Transaction) error { err = doTransaction(sqlDB, func(tx Transaction) error {
// run actual migration // run actual migration
if _, err := tx.Exec(migration["up"]); err != nil { if _, err := tx.Exec(migration["up"]); err != nil {
return err return err
@ -224,7 +213,7 @@ func MigrateCommand(ctx *cli.Context) error {
func findMigrationFiles(dir string, re *regexp.Regexp) ([]string, error) { func findMigrationFiles(dir string, re *regexp.Regexp) ([]string, error) {
files, err := ioutil.ReadDir(dir) files, err := ioutil.ReadDir(dir)
if err != nil { if err != nil {
return nil, fmt.Errorf("Could not find migrations directory `%s`.", dir) return nil, fmt.Errorf("could not find migrations directory `%s`", dir)
} }
matches := []string{} matches := []string{}
@ -260,7 +249,7 @@ func findMigrationFile(dir string, ver string) (string, error) {
} }
if len(files) == 0 { if len(files) == 0 {
return "", fmt.Errorf("Can't find migration file: %s*.sql", ver) return "", fmt.Errorf("can't find migration file: %s*.sql", ver)
} }
return files[0], nil return files[0], nil
@ -307,15 +296,15 @@ func parseMigration(path string) (map[string]string, error) {
return migrations, nil return migrations, nil
} }
// RollbackCommand rolls back the most recent migration // Rollback rolls back the most recent migration
func RollbackCommand(ctx *cli.Context) error { func (db *DB) Rollback() error {
drv, db, err := openDatabaseForMigration(ctx) drv, sqlDB, err := db.openDatabaseForMigration()
if err != nil { if err != nil {
return err return err
} }
defer mustClose(db) defer mustClose(sqlDB)
applied, err := drv.SelectMigrations(db, 1) applied, err := drv.SelectMigrations(sqlDB, 1)
if err != nil { if err != nil {
return err return err
} }
@ -326,24 +315,23 @@ func RollbackCommand(ctx *cli.Context) error {
latest = ver latest = ver
} }
if latest == "" { if latest == "" {
return fmt.Errorf("Can't rollback: no migrations have been applied.") return fmt.Errorf("can't rollback: no migrations have been applied")
} }
migrationsDir := ctx.GlobalString("migrations-dir") filename, err := findMigrationFile(db.MigrationsDir, latest)
filename, err := findMigrationFile(migrationsDir, latest)
if err != nil { if err != nil {
return err return err
} }
fmt.Printf("Rolling back: %s\n", filename) fmt.Printf("Rolling back: %s\n", filename)
migration, err := parseMigration(filepath.Join(migrationsDir, filename)) migration, err := parseMigration(filepath.Join(db.MigrationsDir, filename))
if err != nil { if err != nil {
return err return err
} }
// begin transaction // begin transaction
err = doTransaction(db, func(tx Transaction) error { err = doTransaction(sqlDB, func(tx Transaction) error {
// rollback migration // rollback migration
if _, err := tx.Exec(migration["down"]); err != nil { if _, err := tx.Exec(migration["down"]); err != nil {
return err return err

145
db_test.go Normal file
View file

@ -0,0 +1,145 @@
package dbmate
import (
"net/url"
"os"
"path/filepath"
"testing"
"github.com/stretchr/testify/require"
)
var testdataDir string
func newTestDB(t *testing.T, u *url.URL) *DB {
var err error
// only chdir once, because testdata is relative to current directory
if testdataDir == "" {
testdataDir, err = filepath.Abs("./testdata")
require.Nil(t, err)
err = os.Chdir(testdataDir)
require.Nil(t, err)
}
return NewDB(u)
}
func testURLs(t *testing.T) []*url.URL {
return []*url.URL{
postgresTestURL(t),
mySQLTestURL(t),
sqliteTestURL(t),
}
}
func testMigrateURL(t *testing.T, u *url.URL) {
db := newTestDB(t, u)
// drop and recreate database
err := db.Drop()
require.Nil(t, err)
err = db.Create()
require.Nil(t, err)
// migrate
err = db.Migrate()
require.Nil(t, err)
// verify results
sqlDB, err := GetDriverOpen(u)
require.Nil(t, err)
defer mustClose(sqlDB)
count := 0
err = sqlDB.QueryRow(`select count(*) from schema_migrations
where version = '20151129054053'`).Scan(&count)
require.Nil(t, err)
require.Equal(t, 1, count)
err = sqlDB.QueryRow("select count(*) from users").Scan(&count)
require.Nil(t, err)
require.Equal(t, 1, count)
}
func TestMigrate(t *testing.T) {
for _, u := range testURLs(t) {
testMigrateURL(t, u)
}
}
func testUpURL(t *testing.T, u *url.URL) {
db := newTestDB(t, u)
// drop database
err := db.Drop()
require.Nil(t, err)
// create and migrate
err = db.Up()
require.Nil(t, err)
// verify results
sqlDB, err := GetDriverOpen(u)
require.Nil(t, err)
defer mustClose(sqlDB)
count := 0
err = sqlDB.QueryRow(`select count(*) from schema_migrations
where version = '20151129054053'`).Scan(&count)
require.Nil(t, err)
require.Equal(t, 1, count)
err = sqlDB.QueryRow("select count(*) from users").Scan(&count)
require.Nil(t, err)
require.Equal(t, 1, count)
}
func TestUp(t *testing.T) {
for _, u := range testURLs(t) {
testUpURL(t, u)
}
}
func testRollbackURL(t *testing.T, u *url.URL) {
db := newTestDB(t, u)
// drop, recreate, and migrate database
err := db.Drop()
require.Nil(t, err)
err = db.Create()
require.Nil(t, err)
err = db.Migrate()
require.Nil(t, err)
// verify migration
sqlDB, err := GetDriverOpen(u)
require.Nil(t, err)
defer mustClose(sqlDB)
count := 0
err = sqlDB.QueryRow(`select count(*) from schema_migrations
where version = '20151129054053'`).Scan(&count)
require.Nil(t, err)
require.Equal(t, 1, count)
// rollback
err = db.Rollback()
require.Nil(t, err)
// verify rollback
err = sqlDB.QueryRow("select count(*) from schema_migrations").Scan(&count)
require.Nil(t, err)
require.Equal(t, 0, count)
err = sqlDB.QueryRow("select count(*) from users").Scan(&count)
require.NotNil(t, err)
require.Regexp(t, "(does not exist|doesn't exist|no such table)", err.Error())
}
func TestRollback(t *testing.T) {
for _, u := range testURLs(t) {
testRollbackURL(t, u)
}
}

View file

@ -1,4 +1,4 @@
package main package dbmate
import ( import (
"database/sql" "database/sql"
@ -33,7 +33,7 @@ func GetDriver(name string) (Driver, error) {
case "sqlite", "sqlite3": case "sqlite", "sqlite3":
return SQLiteDriver{}, nil return SQLiteDriver{}, nil
default: default:
return nil, fmt.Errorf("Unknown driver: %s", name) return nil, fmt.Errorf("unknown driver: %s", name)
} }
} }

View file

@ -1,4 +1,4 @@
package main package dbmate
import ( import (
"testing" "testing"
@ -22,6 +22,6 @@ func TestGetDriver_MySQL(t *testing.T) {
func TestGetDriver_Error(t *testing.T) { func TestGetDriver_Error(t *testing.T) {
drv, err := GetDriver("foo") drv, err := GetDriver("foo")
require.Equal(t, "Unknown driver: foo", err.Error()) require.Equal(t, "unknown driver: foo", err.Error())
require.Nil(t, drv) require.Nil(t, drv)
} }

91
main.go
View file

@ -1,91 +0,0 @@
package main
import (
"fmt"
"log"
"os"
"github.com/joho/godotenv"
"github.com/urfave/cli"
)
func main() {
loadDotEnv()
app := NewApp()
err := app.Run(os.Args)
if err != nil {
fmt.Fprintf(os.Stderr, "Error: %s\n", err)
os.Exit(1)
}
}
// NewApp creates a new command line app
func NewApp() *cli.App {
app := cli.NewApp()
app.Name = "dbmate"
app.Usage = "A lightweight, framework-independent database migration tool."
app.Version = Version
app.Flags = []cli.Flag{
cli.StringFlag{
Name: "migrations-dir, d",
Value: "./db/migrations",
Usage: "specify the directory containing migration files",
},
cli.StringFlag{
Name: "env, e",
Value: "DATABASE_URL",
Usage: "specify an environment variable containing the database URL",
},
}
app.Commands = []cli.Command{
{
Name: "new",
Aliases: []string{"n"},
Usage: "Generate a new migration file",
Action: NewCommand,
},
{
Name: "up",
Usage: "Create database (if necessary) and migrate to the latest version",
Action: UpCommand,
},
{
Name: "create",
Usage: "Create database",
Action: CreateCommand,
},
{
Name: "drop",
Usage: "Drop database (if it exists)",
Action: DropCommand,
},
{
Name: "migrate",
Usage: "Migrate to the latest version",
Action: MigrateCommand,
},
{
Name: "rollback",
Aliases: []string{"down"},
Usage: "Rollback the most recent migration",
Action: RollbackCommand,
},
}
return app
}
type command func(*cli.Context) error
func loadDotEnv() {
if _, err := os.Stat(".env"); err != nil {
return
}
if err := godotenv.Load(); err != nil {
log.Fatal("Error loading .env file")
}
}

View file

@ -1,4 +1,4 @@
package main package dbmate
import ( import (
"database/sql" "database/sql"
@ -6,7 +6,7 @@ import (
"net/url" "net/url"
"strings" "strings"
_ "github.com/go-sql-driver/mysql" _ "github.com/go-sql-driver/mysql" // mysql driver for database/sql
) )
// MySQLDriver provides top level database functions // MySQLDriver provides top level database functions

View file

@ -1,4 +1,4 @@
package main package dbmate
import ( import (
"database/sql" "database/sql"

View file

@ -1,4 +1,4 @@
package main package dbmate
import ( import (
"database/sql" "database/sql"

View file

@ -1,4 +1,4 @@
package main package dbmate
import ( import (
"database/sql" "database/sql"

View file

@ -1,4 +1,4 @@
package main package dbmate
import ( import (
"database/sql" "database/sql"
@ -7,7 +7,7 @@ import (
"os" "os"
"regexp" "regexp"
_ "github.com/mattn/go-sqlite3" _ "github.com/mattn/go-sqlite3" // sqlite driver for database/sql
) )
// SQLiteDriver provides top level database functions // SQLiteDriver provides top level database functions

View file

@ -1,4 +1,4 @@
package main package dbmate
import ( import (
"database/sql" "database/sql"

View file

@ -1,4 +1,4 @@
package main package dbmate
import ( import (
"io" "io"

View file

@ -1,4 +1,4 @@
package main package dbmate
import ( import (
"net/url" "net/url"

View file

@ -1,4 +1,4 @@
package main package dbmate
// Version of dbmate // Version of dbmate
const Version = "1.2.1" const Version = "1.2.1"