mirror of https://github.com/pressly/goose.git
Merge branch 'master' into optional-migrations
commit
c579bcf2f9
27
.travis.yml
27
.travis.yml
|
@ -1,22 +1,21 @@
|
|||
sudo: false
|
||||
language: go
|
||||
go:
|
||||
- 1.6
|
||||
- tip
|
||||
- 1.8
|
||||
|
||||
install:
|
||||
- go get github.com/go-sql-driver/mysql
|
||||
- go get github.com/lib/pq
|
||||
- go get github.com/mattn/go-sqlite3
|
||||
- go get github.com/ziutek/mymysql/godrv
|
||||
- go get github.com/golang/dep/cmd/dep
|
||||
- dep ensure
|
||||
|
||||
script:
|
||||
- go test
|
||||
- go run ./cmd/goose/main.go -dir=example/migrations sqlite3 sql.db up
|
||||
- go run ./cmd/goose/main.go -dir=example/migrations sqlite3 sql.db version
|
||||
- go run ./cmd/goose/main.go -dir=example/migrations sqlite3 sql.db down
|
||||
- go run ./cmd/goose/main.go -dir=example/migrations sqlite3 sql.db status
|
||||
- go run ./example/migrations-go/cmd/main.go -dir=example/migrations-go sqlite3 go.db up
|
||||
- go run ./example/migrations-go/cmd/main.go -dir=example/migrations-go sqlite3 go.db version
|
||||
- go run ./example/migrations-go/cmd/main.go -dir=example/migrations-go sqlite3 go.db down
|
||||
- go run ./example/migrations-go/cmd/main.go -dir=example/migrations-go sqlite3 go.db status
|
||||
- go build -i -o goose ./cmd/goose
|
||||
- ./goose -dir=examples/sql-migrations sqlite3 sql.db up
|
||||
- ./goose -dir=examples/sql-migrations sqlite3 sql.db version
|
||||
- ./goose -dir=examples/sql-migrations sqlite3 sql.db down
|
||||
- ./goose -dir=examples/sql-migrations sqlite3 sql.db status
|
||||
- go build -i -o custom-goose ./examples/go-migrations
|
||||
- ./custom-goose -dir=examples/go-migrations sqlite3 go.db up
|
||||
- ./custom-goose -dir=examples/go-migrations sqlite3 go.db version
|
||||
- ./custom-goose -dir=examples/go-migrations sqlite3 go.db down
|
||||
- ./custom-goose -dir=examples/go-migrations sqlite3 go.db status
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
[[constraint]]
|
||||
name = "github.com/go-sql-driver/mysql"
|
||||
version = "^1.3.0"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/lib/pq"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/mattn/go-sqlite3"
|
||||
version = "^1.2.0"
|
104
README.md
104
README.md
|
@ -1,23 +1,30 @@
|
|||
# goose
|
||||
|
||||
Goose is a database migration tool. Manage your database's evolution by creating incremental SQL files or Go functions.
|
||||
Goose is a database migration tool. Manage your database schema by creating incremental SQL changes or Go functions.
|
||||
|
||||
[![GoDoc Widget]][GoDoc] [![Travis Widget]][Travis]
|
||||
|
||||
### Goals of this fork
|
||||
|
||||
github.com/pressly/goose is a fork of bitbucket.org/liamstask/goose with the following changes:
|
||||
`github.com/pressly/goose` is a fork of `bitbucket.org/liamstask/goose` with the following changes:
|
||||
- No config files
|
||||
- [Default goose binary](./cmd/goose/main.go) can migrate SQL files only
|
||||
- Go migrations:
|
||||
- We dropped building Go migrations on-the-fly from .go source files
|
||||
- Instead, you can create your own goose binary, import `github.com/pressly/goose`
|
||||
package and run complex Go migrations with your own `*sql.DB` connection
|
||||
- Each Go migration function is called with `*sql.Tx` argument - within its own transaction
|
||||
- The goose pkg is decoupled from the default binary:
|
||||
- goose pkg doesn't register any SQL drivers anymore
|
||||
(no driver `panic()` conflict within your codebase!)
|
||||
- We don't `go build` Go migrations functions on-the-fly
|
||||
from within the goose binary
|
||||
- Instead, we let you
|
||||
[create your own custom goose binary](examples/go-migrations),
|
||||
register your Go migration functions explicitly and run complex
|
||||
migrations with your own `*sql.DB` connection
|
||||
- Go migration functions let you run your code within
|
||||
an SQL transaction, if you use the `*sql.Tx` argument
|
||||
- The goose pkg is decoupled from the binary:
|
||||
- goose pkg doesn't register any SQL drivers anymore,
|
||||
thus no driver `panic()` conflict within your codebase!
|
||||
- goose pkg doesn't have any vendor dependencies anymore
|
||||
- We encourage using sequential versioning of migration files
|
||||
(rather than timestamps-based versioning) to prevent version
|
||||
mismatch and migration colissions
|
||||
|
||||
# Install
|
||||
|
||||
|
@ -30,37 +37,50 @@ This will install the `goose` binary to your `$GOPATH/bin` directory.
|
|||
```
|
||||
Usage: goose [OPTIONS] DRIVER DBSTRING COMMAND
|
||||
|
||||
Examples:
|
||||
goose postgres "user=postgres dbname=postgres sslmode=disable" up
|
||||
goose mysql "user:password@/dbname" down
|
||||
goose sqlite3 ./foo.db status
|
||||
goose redshift "postgres://user:password@qwerty.us-east-1.redshift.amazonaws.com:5439/db" create init sql
|
||||
|
||||
Options:
|
||||
-dir string
|
||||
directory with migration files (default ".")
|
||||
Drivers:
|
||||
postgres
|
||||
mysql
|
||||
sqlite3
|
||||
redshift
|
||||
|
||||
Commands:
|
||||
up Migrate the DB to the most recent version available
|
||||
down Roll back the version by 1
|
||||
redo Re-run the latest migration
|
||||
status Dump the migration status for the current DB
|
||||
dbversion Print the current version of the database
|
||||
create Creates a blank migration template
|
||||
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 next version
|
||||
|
||||
Options:
|
||||
-dir string
|
||||
directory with migration files (default ".")
|
||||
|
||||
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" status
|
||||
goose redshift "postgres://user:password@qwerty.us-east-1.redshift.amazonaws.com:5439/db" status
|
||||
```
|
||||
## create
|
||||
|
||||
Create a new Go migration.
|
||||
Create a new SQL migration.
|
||||
|
||||
$ goose create AddSomeColumns
|
||||
$ goose: created db/migrations/20130106093224_AddSomeColumns.go
|
||||
$ goose create add_some_column sql
|
||||
$ Created new file: 00001_add_some_column.sql
|
||||
|
||||
Edit the newly created script to define the behavior of your migration.
|
||||
Edit the newly created file to define the behavior of your migration.
|
||||
|
||||
You can also create an SQL migration:
|
||||
You can also create a Go migration, if you then invoke it with [your own goose binary](#go-migrations):
|
||||
|
||||
$ goose create AddSomeColumns sql
|
||||
$ goose: created db/migrations/20130106093224_AddSomeColumns.sql
|
||||
$ goose create fetch_user_data go
|
||||
$ Created new file: 00002_fetch_user_data.go
|
||||
|
||||
## up
|
||||
|
||||
|
@ -72,6 +92,13 @@ Apply all available migrations.
|
|||
$ OK 002_next.sql
|
||||
$ OK 003_and_again.go
|
||||
|
||||
## up-to
|
||||
|
||||
Migrate up to a specific version.
|
||||
|
||||
$ goose up-to 20170506082420
|
||||
$ OK 20170506082420_create_table.sql
|
||||
|
||||
## down
|
||||
|
||||
Roll back a single migration from the current version.
|
||||
|
@ -80,6 +107,13 @@ Roll back a single migration from the current version.
|
|||
$ goose: migrating db environment 'development', current version: 3, target: 2
|
||||
$ OK 003_and_again.go
|
||||
|
||||
## down-to
|
||||
|
||||
Roll back migrations to a specific version.
|
||||
|
||||
$ goose down-to 20170506082527
|
||||
$ OK 20170506082527_alter_column.sql
|
||||
|
||||
## redo
|
||||
|
||||
Roll back the most recently applied migration, then run it again.
|
||||
|
@ -104,12 +138,12 @@ Print the status of all migrations:
|
|||
|
||||
Note: for MySQL [parseTime flag](https://github.com/go-sql-driver/mysql#parsetime) must be enabled.
|
||||
|
||||
## dbversion
|
||||
## version
|
||||
|
||||
Print the current version of the database:
|
||||
|
||||
$ goose dbversion
|
||||
$ goose: dbversion 002
|
||||
$ goose version
|
||||
$ goose: version 002
|
||||
|
||||
# Migrations
|
||||
|
||||
|
@ -169,7 +203,7 @@ language plpgsql;
|
|||
|
||||
## Go Migrations
|
||||
|
||||
1. Create your own goose binary, see [example](./example/migrations-go/cmd/main.go)
|
||||
1. Create your own goose binary, see [example](./examples/go-migrations)
|
||||
2. Import `github.com/pressly/goose`
|
||||
3. Register your migration functions
|
||||
4. Run goose command, ie. `goose.Up(db *sql.DB, dir string)`
|
||||
|
|
|
@ -9,6 +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"
|
||||
|
@ -88,22 +89,35 @@ func usage() {
|
|||
var (
|
||||
usagePrefix = `Usage: goose [OPTIONS] DRIVER DBSTRING COMMAND
|
||||
|
||||
Drivers:
|
||||
postgres
|
||||
mysql
|
||||
sqlite3
|
||||
redshift
|
||||
|
||||
Examples:
|
||||
goose postgres "user=postgres dbname=postgres sslmode=disable" up
|
||||
goose mysql "user:password@/dbname" down
|
||||
goose sqlite3 ./foo.db status
|
||||
goose redshift "postgres://user:password@qwerty.us-east-1.redshift.amazonaws.com:5439/db" create init sql
|
||||
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" 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
|
||||
down Roll back the version by 1
|
||||
redo Re-run the latest migration
|
||||
status Dump the migration status for the current DB
|
||||
dbversion Print the current version of the database
|
||||
create Creates a blank migration template
|
||||
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 next version
|
||||
`
|
||||
)
|
||||
|
|
76
create.go
76
create.go
|
@ -3,16 +3,86 @@ package goose
|
|||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"time"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"text/template"
|
||||
)
|
||||
|
||||
// Create writes a new blank migration file.
|
||||
func Create(db *sql.DB, dir, name, migrationType string) error {
|
||||
path, err := CreateMigration(name, migrationType, dir, time.Now())
|
||||
migrations, err := CollectMigrations(dir, minVersion, maxVersion)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println(fmt.Sprintf("Created %s migration at %s", migrationType, path))
|
||||
|
||||
// Initial version.
|
||||
version := "00001"
|
||||
|
||||
if last, err := migrations.Last(); err == nil {
|
||||
version = fmt.Sprintf("%05v", last.Version+1)
|
||||
}
|
||||
|
||||
filename := fmt.Sprintf("%v_%v.%v", version, name, migrationType)
|
||||
|
||||
fpath := filepath.Join(dir, filename)
|
||||
tmpl := sqlMigrationTemplate
|
||||
if migrationType == "go" {
|
||||
tmpl = goSQLMigrationTemplate
|
||||
}
|
||||
|
||||
path, err := writeTemplateToFile(fpath, tmpl, version)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Printf("Created new file: %s\n", path)
|
||||
return nil
|
||||
}
|
||||
|
||||
func writeTemplateToFile(path string, t *template.Template, version string) (string, error) {
|
||||
if _, err := os.Stat(path); !os.IsNotExist(err) {
|
||||
return "", fmt.Errorf("failed to create file: %v already exists", path)
|
||||
}
|
||||
|
||||
f, err := os.Create(path)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
err = t.Execute(f, version)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return f.Name(), nil
|
||||
}
|
||||
|
||||
var sqlMigrationTemplate = template.Must(template.New("goose.sql-migration").Parse(`-- +goose Up
|
||||
-- SQL in this section is executed when the migration is applied.
|
||||
|
||||
-- +goose Down
|
||||
-- SQL in this section is executed when the migration is rolled back.
|
||||
`))
|
||||
|
||||
var goSQLMigrationTemplate = template.Must(template.New("goose.go-migration").Parse(`package migration
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"github.com/pressly/goose"
|
||||
)
|
||||
|
||||
func init() {
|
||||
goose.AddMigration(Up{{.}}, Down{{.}})
|
||||
}
|
||||
|
||||
func Up{{.}}(tx *sql.Tx) error {
|
||||
// This code is executed when the migration is applied.
|
||||
return nil
|
||||
}
|
||||
|
||||
func Down{{.}}(tx *sql.Tx) error {
|
||||
// This code is executed when the migration is rolled back.
|
||||
return nil
|
||||
}
|
||||
`))
|
||||
|
|
40
dialect.go
40
dialect.go
|
@ -5,26 +5,28 @@ import (
|
|||
"fmt"
|
||||
)
|
||||
|
||||
// SqlDialect abstracts the details of specific SQL dialects
|
||||
// SQLDialect abstracts the details of specific SQL dialects
|
||||
// for goose's few SQL specific statements
|
||||
type SqlDialect interface {
|
||||
createVersionTableSql() string // sql string to create the goose_db_version table
|
||||
insertVersionSql() string // sql string to insert the initial version table row
|
||||
type SQLDialect interface {
|
||||
createVersionTableSQL() string // sql string to create the goose_db_version table
|
||||
insertVersionSQL() string // sql string to insert the initial version table row
|
||||
dbVersionQuery(db *sql.DB) (*sql.Rows, error)
|
||||
}
|
||||
|
||||
var dialect SqlDialect = &PostgresDialect{}
|
||||
var dialect SQLDialect = &PostgresDialect{}
|
||||
|
||||
func GetDialect() SqlDialect {
|
||||
// GetDialect gets the SQLDialect
|
||||
func GetDialect() SQLDialect {
|
||||
return dialect
|
||||
}
|
||||
|
||||
// SetDialect sets the SQLDialect
|
||||
func SetDialect(d string) error {
|
||||
switch d {
|
||||
case "postgres":
|
||||
dialect = &PostgresDialect{}
|
||||
case "mysql":
|
||||
dialect = &MySqlDialect{}
|
||||
dialect = &MySQLDialect{}
|
||||
case "sqlite3":
|
||||
dialect = &Sqlite3Dialect{}
|
||||
case "redshift":
|
||||
|
@ -40,9 +42,10 @@ func SetDialect(d string) error {
|
|||
// Postgres
|
||||
////////////////////////////
|
||||
|
||||
// PostgresDialect struct.
|
||||
type PostgresDialect struct{}
|
||||
|
||||
func (pg PostgresDialect) createVersionTableSql() string {
|
||||
func (pg PostgresDialect) createVersionTableSQL() string {
|
||||
return `CREATE TABLE goose_db_version (
|
||||
id serial NOT NULL,
|
||||
version_id bigint NOT NULL,
|
||||
|
@ -52,7 +55,7 @@ func (pg PostgresDialect) createVersionTableSql() string {
|
|||
);`
|
||||
}
|
||||
|
||||
func (pg PostgresDialect) insertVersionSql() string {
|
||||
func (pg PostgresDialect) insertVersionSQL() string {
|
||||
return "INSERT INTO goose_db_version (version_id, is_applied) VALUES ($1, $2);"
|
||||
}
|
||||
|
||||
|
@ -69,9 +72,10 @@ func (pg PostgresDialect) dbVersionQuery(db *sql.DB) (*sql.Rows, error) {
|
|||
// MySQL
|
||||
////////////////////////////
|
||||
|
||||
type MySqlDialect struct{}
|
||||
// MySQLDialect struct.
|
||||
type MySQLDialect struct{}
|
||||
|
||||
func (m MySqlDialect) createVersionTableSql() string {
|
||||
func (m MySQLDialect) createVersionTableSQL() string {
|
||||
return `CREATE TABLE goose_db_version (
|
||||
id serial NOT NULL,
|
||||
version_id bigint NOT NULL,
|
||||
|
@ -81,11 +85,11 @@ func (m MySqlDialect) createVersionTableSql() string {
|
|||
);`
|
||||
}
|
||||
|
||||
func (m MySqlDialect) insertVersionSql() string {
|
||||
func (m MySQLDialect) insertVersionSQL() string {
|
||||
return "INSERT INTO goose_db_version (version_id, is_applied) VALUES (?, ?);"
|
||||
}
|
||||
|
||||
func (m MySqlDialect) dbVersionQuery(db *sql.DB) (*sql.Rows, error) {
|
||||
func (m MySQLDialect) dbVersionQuery(db *sql.DB) (*sql.Rows, error) {
|
||||
rows, err := db.Query("SELECT version_id, is_applied from goose_db_version ORDER BY id DESC")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -98,9 +102,10 @@ func (m MySqlDialect) dbVersionQuery(db *sql.DB) (*sql.Rows, error) {
|
|||
// sqlite3
|
||||
////////////////////////////
|
||||
|
||||
// Sqlite3Dialect struct.
|
||||
type Sqlite3Dialect struct{}
|
||||
|
||||
func (m Sqlite3Dialect) createVersionTableSql() string {
|
||||
func (m Sqlite3Dialect) createVersionTableSQL() string {
|
||||
return `CREATE TABLE goose_db_version (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
version_id INTEGER NOT NULL,
|
||||
|
@ -109,7 +114,7 @@ func (m Sqlite3Dialect) createVersionTableSql() string {
|
|||
);`
|
||||
}
|
||||
|
||||
func (m Sqlite3Dialect) insertVersionSql() string {
|
||||
func (m Sqlite3Dialect) insertVersionSQL() string {
|
||||
return "INSERT INTO goose_db_version (version_id, is_applied) VALUES (?, ?);"
|
||||
}
|
||||
|
||||
|
@ -126,9 +131,10 @@ func (m Sqlite3Dialect) dbVersionQuery(db *sql.DB) (*sql.Rows, error) {
|
|||
// Redshift
|
||||
////////////////////////////
|
||||
|
||||
// RedshiftDialect struct.
|
||||
type RedshiftDialect struct{}
|
||||
|
||||
func (rs RedshiftDialect) createVersionTableSql() string {
|
||||
func (rs RedshiftDialect) createVersionTableSQL() string {
|
||||
return `CREATE TABLE goose_db_version (
|
||||
id integer NOT NULL identity(1, 1),
|
||||
version_id bigint NOT NULL,
|
||||
|
@ -138,7 +144,7 @@ func (rs RedshiftDialect) createVersionTableSql() string {
|
|||
);`
|
||||
}
|
||||
|
||||
func (rs RedshiftDialect) insertVersionSql() string {
|
||||
func (rs RedshiftDialect) insertVersionSQL() string {
|
||||
return "INSERT INTO goose_db_version (version_id, is_applied) VALUES ($1, $2);"
|
||||
}
|
||||
|
||||
|
|
20
down.go
20
down.go
|
@ -5,6 +5,7 @@ import (
|
|||
"fmt"
|
||||
)
|
||||
|
||||
// Down rolls back a single migration from the current version.
|
||||
func Down(db *sql.DB, dir string) error {
|
||||
currentVersion, err := GetDBVersion(db)
|
||||
if err != nil {
|
||||
|
@ -24,6 +25,7 @@ func Down(db *sql.DB, dir string) error {
|
|||
return current.Down(db)
|
||||
}
|
||||
|
||||
// DownTo rolls back migrations to a specific version.
|
||||
func DownTo(db *sql.DB, dir string, version int64) error {
|
||||
migrations, err := CollectMigrations(dir, minVersion, maxVersion)
|
||||
if err != nil {
|
||||
|
@ -36,29 +38,19 @@ func DownTo(db *sql.DB, dir string, version int64) error {
|
|||
return err
|
||||
}
|
||||
|
||||
prev, err := migrations.Previous(currentVersion)
|
||||
current, err := migrations.Current(currentVersion)
|
||||
if err != nil {
|
||||
if err == ErrNoNextVersion {
|
||||
fmt.Printf("goose: no migrations to run. current version: %d\n", currentVersion)
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
if prev.Version < version {
|
||||
fmt.Printf("goose: no migrations to run. current version: %d\n", currentVersion)
|
||||
return nil
|
||||
}
|
||||
|
||||
current, err := migrations.Current(currentVersion)
|
||||
if err != nil {
|
||||
return fmt.Errorf("no migration %v", currentVersion)
|
||||
if current.Version <= version {
|
||||
fmt.Printf("goose: no migrations to run. current version: %d\n", currentVersion)
|
||||
return nil
|
||||
}
|
||||
|
||||
if err = current.Down(db); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
# 1. [SQL migrations](sql-migrations)
|
||||
# 2. [Go migrations](go-migrations)
|
|
@ -1,4 +1,4 @@
|
|||
package migrations
|
||||
package main
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
|
@ -7,10 +7,10 @@ import (
|
|||
)
|
||||
|
||||
func init() {
|
||||
goose.AddMigration(Up, Down)
|
||||
goose.AddMigration(Up00002, Down00002)
|
||||
}
|
||||
|
||||
func Up(tx *sql.Tx) error {
|
||||
func Up00002(tx *sql.Tx) error {
|
||||
_, err := tx.Exec("UPDATE users SET username='admin' WHERE username='root';")
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -18,7 +18,7 @@ func Up(tx *sql.Tx) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func Down(tx *sql.Tx) error {
|
||||
func Down00002(tx *sql.Tx) error {
|
||||
_, err := tx.Exec("UPDATE users SET username='root' WHERE username='admin';")
|
||||
if err != nil {
|
||||
return err
|
|
@ -0,0 +1,41 @@
|
|||
# SQL + Go migrations
|
||||
|
||||
## This example: Custom goose binary with built-in Go migrations
|
||||
|
||||
```bash
|
||||
$ go build -o goose *.go
|
||||
```
|
||||
|
||||
```
|
||||
$ ./goose sqlite3 ./foo.db status
|
||||
Applied At Migration
|
||||
=======================================
|
||||
Pending -- 00001_create_users_table.sql
|
||||
Pending -- 00002_rename_root.go
|
||||
|
||||
$ ./goose sqlite3 ./foo.db up
|
||||
OK 00001_create_users_table.sql
|
||||
OK 00002_rename_root.go
|
||||
goose: no migrations to run. current version: 2
|
||||
|
||||
$
|
||||
Applied At Migration
|
||||
=======================================
|
||||
Mon Jun 19 21:56:00 2017 -- 00001_create_users_table.sql
|
||||
Mon Jun 19 21:56:00 2017 -- 00002_rename_root.go
|
||||
```
|
||||
|
||||
## Best practice: Split migrations into a standalone package
|
||||
|
||||
1. Move [main.go](main.go) into your `cmd/` directory
|
||||
|
||||
2. Rename package name in all `*_.go` migration files from `main` to `migrations`.
|
||||
|
||||
3. Import this `migrations` package from your custom [cmd/main.go](main.go) file:
|
||||
|
||||
```go
|
||||
import (
|
||||
// Invoke init() functions within migrations pkg.
|
||||
_ "github.com/pressly/goose/example/migrations-go"
|
||||
)
|
||||
```
|
Binary file not shown.
Binary file not shown.
|
@ -9,8 +9,7 @@ import (
|
|||
|
||||
"github.com/pressly/goose"
|
||||
|
||||
_ "github.com/pressly/goose/example/migrations-go"
|
||||
|
||||
// Init DB drivers.
|
||||
_ "github.com/go-sql-driver/mysql"
|
||||
_ "github.com/lib/pq"
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
|
@ -27,7 +26,15 @@ func main() {
|
|||
flags.Parse(os.Args[1:])
|
||||
|
||||
args := flags.Args()
|
||||
if len(args) != 3 {
|
||||
|
||||
if len(args) > 1 && args[0] == "create" {
|
||||
if err := goose.Run("create", nil, *dir, args[1:]...); err != nil {
|
||||
log.Fatalf("goose run: %v", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if len(args) < 3 {
|
||||
flags.Usage()
|
||||
return
|
||||
}
|
||||
|
@ -63,7 +70,12 @@ func main() {
|
|||
log.Fatalf("-dbstring=%q: %v\n", dbstring, err)
|
||||
}
|
||||
|
||||
if err := goose.Run(command, db, *dir); err != nil {
|
||||
arguments := []string{}
|
||||
if len(args) > 3 {
|
||||
arguments = append(arguments, args[3:]...)
|
||||
}
|
||||
|
||||
if err := goose.Run(command, db, *dir, arguments...); err != nil {
|
||||
log.Fatalf("goose run: %v", err)
|
||||
}
|
||||
}
|
||||
|
@ -77,21 +89,35 @@ func usage() {
|
|||
var (
|
||||
usagePrefix = `Usage: goose [OPTIONS] DRIVER DBSTRING COMMAND
|
||||
|
||||
Drivers:
|
||||
postgres
|
||||
mysql
|
||||
sqlite3
|
||||
redshift
|
||||
|
||||
Examples:
|
||||
goose postgres "user=postgres dbname=postgres sslmode=disable" up
|
||||
goose mysql "user:password@/dbname" down
|
||||
goose sqlite3 ./foo.db status
|
||||
goose redshift "postgres://user:password@qwerty.us-east-1.redshift.amazonaws.com:5439/db" create init sql
|
||||
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" 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
|
||||
down Roll back the version by 1
|
||||
redo Re-run the latest migration
|
||||
status Dump the migration status for the current DB
|
||||
dbversion Print the current version of the database
|
||||
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 next version
|
||||
`
|
||||
)
|
|
@ -0,0 +1,26 @@
|
|||
# SQL migrations only
|
||||
|
||||
See [this example](../go-migrations) for Go migrations.
|
||||
|
||||
```bash
|
||||
$ go get -u github.com/pressly/goose/cmd/goose
|
||||
```
|
||||
|
||||
```bash
|
||||
$ goose sqlite3 ./foo.db status
|
||||
Applied At Migration
|
||||
=======================================
|
||||
Pending -- 00001_create_users_table.sql
|
||||
Pending -- 00002_rename_root.sql
|
||||
|
||||
$ goose sqlite3 ./foo.db up
|
||||
OK 00001_create_users_table.sql
|
||||
OK 00002_rename_root.sql
|
||||
goose: no migrations to run. current version: 2
|
||||
|
||||
$ goose sqlite3 ./foo.db status
|
||||
Applied At Migration
|
||||
=======================================
|
||||
Mon Jun 19 21:56:00 2017 -- 00001_create_users_table.sql
|
||||
Mon Jun 19 21:56:00 2017 -- 00002_rename_root.sql
|
||||
```
|
3
goose.go
3
goose.go
|
@ -3,8 +3,8 @@ package goose
|
|||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"sync"
|
||||
"strconv"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -13,6 +13,7 @@ var (
|
|||
maxVersion = int64((1 << 63) - 1)
|
||||
)
|
||||
|
||||
// Run runs a goose command.
|
||||
func Run(command string, db *sql.DB, dir string, args ...string) error {
|
||||
switch command {
|
||||
case "up":
|
||||
|
|
74
migrate.go
74
migrate.go
|
@ -11,14 +11,17 @@ import (
|
|||
)
|
||||
|
||||
var (
|
||||
// ErrNoCurrentVersion when a current migration version is not found.
|
||||
ErrNoCurrentVersion = errors.New("no current version found")
|
||||
ErrNoNextVersion = errors.New("no next version found")
|
||||
|
||||
// ErrNoNextVersion when the next migration version is not found.
|
||||
ErrNoNextVersion = errors.New("no next version found")
|
||||
// MaxVersion is the maximum allowed version.
|
||||
MaxVersion int64 = 9223372036854775807 // max(int64)
|
||||
|
||||
goMigrations []*Migration
|
||||
registeredGoMigrations = map[int64]*Migration{}
|
||||
)
|
||||
|
||||
// Migrations slice.
|
||||
type Migrations []*Migration
|
||||
|
||||
// helpers so we can use pkg sort
|
||||
|
@ -31,6 +34,7 @@ func (ms Migrations) Less(i, j int) bool {
|
|||
return ms[i].Version < ms[j].Version
|
||||
}
|
||||
|
||||
// Current gets the current migration.
|
||||
func (ms Migrations) Current(current int64) (*Migration, error) {
|
||||
for i, migration := range ms {
|
||||
if migration.Version == current {
|
||||
|
@ -41,6 +45,7 @@ func (ms Migrations) Current(current int64) (*Migration, error) {
|
|||
return nil, ErrNoCurrentVersion
|
||||
}
|
||||
|
||||
// Next gets the next migration.
|
||||
func (ms Migrations) Next(current int64) (*Migration, error) {
|
||||
for i, migration := range ms {
|
||||
if migration.Version > current {
|
||||
|
@ -51,8 +56,9 @@ func (ms Migrations) Next(current int64) (*Migration, error) {
|
|||
return nil, ErrNoNextVersion
|
||||
}
|
||||
|
||||
// Previous : Get the previous migration.
|
||||
func (ms Migrations) Previous(current int64) (*Migration, error) {
|
||||
for i := len(ms)-1; i >= 0; i-- {
|
||||
for i := len(ms) - 1; i >= 0; i-- {
|
||||
if ms[i].Version < current {
|
||||
return ms[i], nil
|
||||
}
|
||||
|
@ -61,6 +67,7 @@ func (ms Migrations) Previous(current int64) (*Migration, error) {
|
|||
return nil, ErrNoNextVersion
|
||||
}
|
||||
|
||||
// Last gets the last migration.
|
||||
func (ms Migrations) Last() (*Migration, error) {
|
||||
if len(ms) == 0 {
|
||||
return nil, ErrNoNextVersion
|
||||
|
@ -77,16 +84,21 @@ func (ms Migrations) String() string {
|
|||
return str
|
||||
}
|
||||
|
||||
// AddMigration adds a migration.
|
||||
func AddMigration(up func(*sql.Tx) error, down func(*sql.Tx) error) {
|
||||
_, filename, _, _ := runtime.Caller(1)
|
||||
AddNamedMigration(filename, up, down)
|
||||
}
|
||||
|
||||
// AddNamedMigration : Add a named migration.
|
||||
func AddNamedMigration(filename string, up func(*sql.Tx) error, down func(*sql.Tx) error) {
|
||||
v, _ := NumericComponent(filename)
|
||||
migration := &Migration{Version: v, Next: -1, Previous: -1, UpFn: up, DownFn: down, Source: filename}
|
||||
migration := &Migration{Version: v, Next: -1, Previous: -1, Registered: true, UpFn: up, DownFn: down, Source: filename}
|
||||
|
||||
goMigrations = append(goMigrations, migration)
|
||||
if existing, ok := registeredGoMigrations[v]; ok {
|
||||
panic(fmt.Sprintf("failed to add migration %q: version conflicts with %q", filename, existing.Source))
|
||||
}
|
||||
registeredGoMigrations[v] = migration
|
||||
}
|
||||
|
||||
// CollectMigrations returns all the valid looking migration scripts in the
|
||||
|
@ -94,15 +106,12 @@ func AddNamedMigration(filename string, up func(*sql.Tx) error, down func(*sql.T
|
|||
func CollectMigrations(dirpath string, current, target int64) (Migrations, error) {
|
||||
var migrations Migrations
|
||||
|
||||
// extract the numeric component of each migration,
|
||||
// filter out any uninteresting files,
|
||||
// and ensure we only have one file per migration version.
|
||||
sqlMigrations, err := filepath.Glob(dirpath + "/*.sql")
|
||||
// SQL migration files.
|
||||
sqlMigrationFiles, err := filepath.Glob(dirpath + "/**.sql")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, file := range sqlMigrations {
|
||||
for _, file := range sqlMigrationFiles {
|
||||
v, err := NumericComponent(file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -113,7 +122,8 @@ func CollectMigrations(dirpath string, current, target int64) (Migrations, error
|
|||
}
|
||||
}
|
||||
|
||||
for _, migration := range goMigrations {
|
||||
// Go migrations registered via goose.AddMigration().
|
||||
for _, migration := range registeredGoMigrations {
|
||||
v, err := NumericComponent(migration.Source)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -123,6 +133,26 @@ func CollectMigrations(dirpath string, current, target int64) (Migrations, error
|
|||
}
|
||||
}
|
||||
|
||||
// Go migration files
|
||||
goMigrationFiles, err := filepath.Glob(dirpath + "/**.go")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, file := range goMigrationFiles {
|
||||
v, err := NumericComponent(file)
|
||||
if err != nil {
|
||||
continue // Skip any files that don't have start with version.
|
||||
}
|
||||
// Skip migrations already registered via goose.AddMigration().
|
||||
if _, ok := registeredGoMigrations[v]; ok {
|
||||
continue
|
||||
}
|
||||
if versionFilter(v, current, target) {
|
||||
migration := &Migration{Version: v, Next: -1, Previous: -1, Source: file, Registered: false}
|
||||
migrations = append(migrations, migration)
|
||||
}
|
||||
}
|
||||
|
||||
migrations = sortAndConnectMigrations(migrations)
|
||||
|
||||
return migrations, nil
|
||||
|
@ -158,7 +188,7 @@ func versionFilter(v, current, target int64) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
// retrieve the current version for this DB.
|
||||
// EnsureDBVersion retrieves the current version for this DB.
|
||||
// Create and initialize the DB version table if it doesn't exist.
|
||||
func EnsureDBVersion(db *sql.DB) (int64, error) {
|
||||
rows, err := GetDialect().dbVersionQuery(db)
|
||||
|
@ -175,14 +205,14 @@ func EnsureDBVersion(db *sql.DB) (int64, error) {
|
|||
|
||||
for rows.Next() {
|
||||
var row MigrationRecord
|
||||
if err = rows.Scan(&row.VersionId, &row.IsApplied); err != nil {
|
||||
if err = rows.Scan(&row.VersionID, &row.IsApplied); err != nil {
|
||||
log.Fatal("error scanning rows:", err)
|
||||
}
|
||||
|
||||
// have we already marked this version to be skipped?
|
||||
skip := false
|
||||
for _, v := range toSkip {
|
||||
if v == row.VersionId {
|
||||
if v == row.VersionID {
|
||||
skip = true
|
||||
break
|
||||
}
|
||||
|
@ -194,11 +224,11 @@ func EnsureDBVersion(db *sql.DB) (int64, error) {
|
|||
|
||||
// if version has been applied we're done
|
||||
if row.IsApplied {
|
||||
return row.VersionId, nil
|
||||
return row.VersionID, nil
|
||||
}
|
||||
|
||||
// latest version of migration has not been applied.
|
||||
toSkip = append(toSkip, row.VersionId)
|
||||
toSkip = append(toSkip, row.VersionID)
|
||||
}
|
||||
|
||||
return 0, ErrNoNextVersion
|
||||
|
@ -214,14 +244,14 @@ func createVersionTable(db *sql.DB) error {
|
|||
|
||||
d := GetDialect()
|
||||
|
||||
if _, err := txn.Exec(d.createVersionTableSql()); err != nil {
|
||||
if _, err := txn.Exec(d.createVersionTableSQL()); err != nil {
|
||||
txn.Rollback()
|
||||
return err
|
||||
}
|
||||
|
||||
version := 0
|
||||
applied := true
|
||||
if _, err := txn.Exec(d.insertVersionSql(), version, applied); err != nil {
|
||||
if _, err := txn.Exec(d.insertVersionSQL(), version, applied); err != nil {
|
||||
txn.Rollback()
|
||||
return err
|
||||
}
|
||||
|
@ -229,8 +259,8 @@ func createVersionTable(db *sql.DB) error {
|
|||
return txn.Commit()
|
||||
}
|
||||
|
||||
// wrapper for EnsureDBVersion for callers that don't already have
|
||||
// their own DB instance
|
||||
// GetDBVersion is a wrapper for EnsureDBVersion for callers that don't already
|
||||
// have their own DB instance
|
||||
func GetDBVersion(db *sql.DB) (int64, error) {
|
||||
version, err := EnsureDBVersion(db)
|
||||
if err != nil {
|
||||
|
|
87
migration.go
87
migration.go
|
@ -8,33 +8,37 @@ import (
|
|||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"text/template"
|
||||
"time"
|
||||
)
|
||||
|
||||
// MigrationRecord struct.
|
||||
type MigrationRecord struct {
|
||||
VersionId int64
|
||||
VersionID int64
|
||||
TStamp time.Time
|
||||
IsApplied bool // was this a result of up() or down()
|
||||
}
|
||||
|
||||
// Migration struct.
|
||||
type Migration struct {
|
||||
Version int64
|
||||
Next int64 // next version, or -1 if none
|
||||
Previous int64 // previous version, -1 if none
|
||||
Source string // path to .sql script
|
||||
UpFn func(*sql.Tx) error // Up go migration function
|
||||
DownFn func(*sql.Tx) error // Down go migration function
|
||||
Version int64
|
||||
Next int64 // next version, or -1 if none
|
||||
Previous int64 // previous version, -1 if none
|
||||
Source string // path to .sql script
|
||||
Registered bool
|
||||
UpFn func(*sql.Tx) error // Up go migration function
|
||||
DownFn func(*sql.Tx) error // Down go migration function
|
||||
}
|
||||
|
||||
func (m *Migration) String() string {
|
||||
return fmt.Sprintf(m.Source)
|
||||
}
|
||||
|
||||
// Up runs an up migration.
|
||||
func (m *Migration) Up(db *sql.DB) error {
|
||||
return m.run(db, true)
|
||||
}
|
||||
|
||||
// Down runs a down migration.
|
||||
func (m *Migration) Down(db *sql.DB) error {
|
||||
return m.run(db, false)
|
||||
}
|
||||
|
@ -43,10 +47,13 @@ func (m *Migration) run(db *sql.DB, direction bool) error {
|
|||
switch filepath.Ext(m.Source) {
|
||||
case ".sql":
|
||||
if err := runSQLMigration(db, m.Source, m.Version, direction); err != nil {
|
||||
return errors.New(fmt.Sprintf("FAIL %v, quitting migration", err))
|
||||
return fmt.Errorf("FAIL %v, quitting migration", err)
|
||||
}
|
||||
|
||||
case ".go":
|
||||
if !m.Registered {
|
||||
log.Fatalf("failed to apply Go migration %q: Go functions must be registered and built into a custom binary (see https://github.com/pressly/goose/tree/master/examples/go-migrations)", m.Source)
|
||||
}
|
||||
tx, err := db.Begin()
|
||||
if err != nil {
|
||||
log.Fatal("db.Begin: ", err)
|
||||
|
@ -70,9 +77,8 @@ func (m *Migration) run(db *sql.DB, direction bool) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// look for migration scripts with names in the form:
|
||||
// XXX_descriptivename.ext
|
||||
// where XXX specifies the version number
|
||||
// NumericComponent looks for migration scripts with names in the form:
|
||||
// XXX_descriptivename.ext where XXX specifies the version number
|
||||
// and ext specifies the type of migration
|
||||
func NumericComponent(name string) (int64, error) {
|
||||
|
||||
|
@ -95,32 +101,12 @@ func NumericComponent(name string) (int64, error) {
|
|||
return n, e
|
||||
}
|
||||
|
||||
func CreateMigration(name, migrationType, dir string, t time.Time) (path string, err error) {
|
||||
|
||||
if migrationType != "go" && migrationType != "sql" {
|
||||
return "", errors.New("migration type must be 'go' or 'sql'")
|
||||
}
|
||||
|
||||
timestamp := t.Format("20060102150405")
|
||||
filename := fmt.Sprintf("%v_%v.%v", timestamp, name, migrationType)
|
||||
|
||||
fpath := filepath.Join(dir, filename)
|
||||
tmpl := sqlMigrationTemplate
|
||||
if migrationType == "go" {
|
||||
tmpl = goSqlMigrationTemplate
|
||||
}
|
||||
|
||||
path, err = writeTemplateToFile(fpath, tmpl, timestamp)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Update the version table for the given migration,
|
||||
// FinalizeMigration updates the version table for the given migration,
|
||||
// and finalize the transaction.
|
||||
func FinalizeMigrationTx(tx *sql.Tx, direction bool, v int64) error {
|
||||
|
||||
// XXX: drop goose_db_version table on some minimum version number?
|
||||
stmt := GetDialect().insertVersionSql()
|
||||
stmt := GetDialect().insertVersionSQL()
|
||||
if _, err := tx.Exec(stmt, v, direction); err != nil {
|
||||
tx.Rollback()
|
||||
return err
|
||||
|
@ -140,36 +126,3 @@ func FinalizeMigration(db *sql.DB, direction bool, v int64) error {
|
|||
|
||||
return nil
|
||||
}
|
||||
|
||||
var sqlMigrationTemplate = template.Must(template.New("goose.sql-migration").Parse(`
|
||||
-- +goose Up
|
||||
-- SQL in section 'Up' is executed when this migration is applied
|
||||
|
||||
|
||||
-- +goose Down
|
||||
-- SQL section 'Down' is executed when this migration is rolled back
|
||||
|
||||
`))
|
||||
var goSqlMigrationTemplate = template.Must(template.New("goose.go-migration").Parse(`
|
||||
package migration
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
|
||||
"github.com/pressly/goose"
|
||||
)
|
||||
|
||||
func init() {
|
||||
goose.AddMigration(Up{{.}}, Down{{.}})
|
||||
}
|
||||
|
||||
// Up{{.}} updates the database to the new requirements
|
||||
func Up{{.}}(tx *sql.Tx) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Down{{.}} should send the database back to the state it was from before Up was ran
|
||||
func Down{{.}}(tx *sql.Tx) error {
|
||||
return nil
|
||||
}
|
||||
`))
|
||||
|
|
1
redo.go
1
redo.go
|
@ -4,6 +4,7 @@ import (
|
|||
"database/sql"
|
||||
)
|
||||
|
||||
// Redo rolls back the most recently applied migration, then runs it again.
|
||||
func Redo(db *sql.DB, dir string) error {
|
||||
currentVersion, err := GetDBVersion(db)
|
||||
if err != nil {
|
||||
|
|
|
@ -8,6 +8,7 @@ import (
|
|||
"time"
|
||||
)
|
||||
|
||||
// Status prints the status of all migrations.
|
||||
func Status(db *sql.DB, dir string) error {
|
||||
// collect all migrations
|
||||
migrations, err := CollectMigrations(dir, minVersion, maxVersion)
|
||||
|
|
5
up.go
5
up.go
|
@ -5,6 +5,7 @@ import (
|
|||
"fmt"
|
||||
)
|
||||
|
||||
// UpTo migrates up to a specific version.
|
||||
func UpTo(db *sql.DB, dir string, version int64) error {
|
||||
migrations, err := CollectMigrations(dir, minVersion, version)
|
||||
if err != nil {
|
||||
|
@ -30,14 +31,14 @@ func UpTo(db *sql.DB, dir string, version int64) error {
|
|||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Up applies all available migrations.
|
||||
func Up(db *sql.DB, dir string) error {
|
||||
return UpTo(db, dir, maxVersion)
|
||||
}
|
||||
|
||||
// UpByOne migrates up by a single version.
|
||||
func UpByOne(db *sql.DB, dir string) error {
|
||||
migrations, err := CollectMigrations(dir, minVersion, maxVersion)
|
||||
if err != nil {
|
||||
|
|
40
util.go
40
util.go
|
@ -1,40 +0,0 @@
|
|||
package goose
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
"text/template"
|
||||
)
|
||||
|
||||
// common routines
|
||||
|
||||
func writeTemplateToFile(path string, t *template.Template, data interface{}) (string, error) {
|
||||
f, e := os.Create(path)
|
||||
if e != nil {
|
||||
return "", e
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
e = t.Execute(f, data)
|
||||
if e != nil {
|
||||
return "", e
|
||||
}
|
||||
|
||||
return f.Name(), nil
|
||||
}
|
||||
|
||||
func copyFile(dst, src string) (int64, error) {
|
||||
sf, err := os.Open(src)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer sf.Close()
|
||||
|
||||
df, err := os.Create(dst)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer df.Close()
|
||||
|
||||
return io.Copy(df, sf)
|
||||
}
|
|
@ -5,12 +5,13 @@ import (
|
|||
"fmt"
|
||||
)
|
||||
|
||||
// Version prints the current version of the database.
|
||||
func Version(db *sql.DB, dir string) error {
|
||||
current, err := GetDBVersion(db)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Printf("goose: dbversion %v\n", current)
|
||||
fmt.Printf("goose: version %v\n", current)
|
||||
return nil
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue