Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add -dry-run flag #479

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ Goose supports [embedding SQL migrations](#embedded-sql-migrations), which means
- goose pkg doesn't have any vendor dependencies anymore
- We use timestamped migrations by default but recommend a hybrid approach of using timestamps in the development process and sequential versions in production.
- Supports missing (out-of-order) migrations with the `-allow-missing` flag, or if using as a library supply the functional option `goose.WithAllowMissing()` to Up, UpTo or UpByOne.
- Supports performing dry runs with the `-dry-run` flag, or if using as a library supply the functional option `goose.WithDryRun()` to Up, UpTo or UpByOne.
- Supports applying ad-hoc migrations without tracking them in the schema table. Useful for seeding a database after migrations have been applied. Use `-no-versioning` flag or the functional option `goose.WithNoVersioning()`.

# Install
Expand Down Expand Up @@ -86,6 +87,8 @@ Options:
file path to root CA's certificates in pem format (only supported on mysql)
-dir string
directory with migration files (default ".")
-dry-run
prints out the migrations it would apply and exits before applying them
-h print help
-no-versioning
apply migration commands with no versioning, in file order, from directory pointed to
Expand Down
4 changes: 4 additions & 0 deletions cmd/goose/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ var (
sslkey = flags.String("ssl-key", "", "file path to SSL key in pem format (only support on mysql)")
noVersioning = flags.Bool("no-versioning", false, "apply migration commands with no versioning, in file order, from directory pointed to")
noColor = flags.Bool("no-color", false, "disable color output (NO_COLOR env variable supported)")
isDryRun = flags.Bool("dry-run", false, "prints out the migrations it would apply and exits before applying them")
)
var (
gooseVersion = ""
Expand Down Expand Up @@ -145,6 +146,9 @@ func main() {
if *allowMissing {
options = append(options, goose.WithAllowMissing())
}
if *isDryRun {
options = append(options, goose.WithDryRun())
}
if *noVersioning {
options = append(options, goose.WithNoVersioning())
}
Expand Down
15 changes: 9 additions & 6 deletions down.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ func Down(db *sql.DB, dir string, opts ...OptionsFunc) error {
}
currentVersion := migrations[len(migrations)-1].Version
// Migrate only the latest migration down.
return downToNoVersioning(db, migrations, currentVersion-1)
return downToNoVersioning(db, migrations, currentVersion-1, option.isDryRun)
}
currentVersion, err := GetDBVersion(db)
if err != nil {
Expand All @@ -31,7 +31,7 @@ func Down(db *sql.DB, dir string, opts ...OptionsFunc) error {
if err != nil {
return fmt.Errorf("no migration %v", currentVersion)
}
return current.Down(db)
return current.Down(db, option.toMigrationOptionsFunc())
}

// DownTo rolls back migrations to a specific version.
Expand All @@ -45,7 +45,7 @@ func DownTo(db *sql.DB, dir string, version int64, opts ...OptionsFunc) error {
return err
}
if option.noVersioning {
return downToNoVersioning(db, migrations, version)
return downToNoVersioning(db, migrations, version, option.isDryRun)
}

for {
Expand All @@ -69,23 +69,26 @@ func DownTo(db *sql.DB, dir string, version int64, opts ...OptionsFunc) error {
return nil
}

if err = current.Down(db); err != nil {
if err = current.Down(db, option.toMigrationOptionsFunc()); err != nil {
return err
}
}
}

// downToNoVersioning applies down migrations down to, but not including, the
// target version.
func downToNoVersioning(db *sql.DB, migrations Migrations, version int64) error {
func downToNoVersioning(db *sql.DB, migrations Migrations, version int64, isDryRun bool) error {
var finalVersion int64
for i := len(migrations) - 1; i >= 0; i-- {
if version >= migrations[i].Version {
finalVersion = migrations[i].Version
break
}
migrations[i].noVersioning = true
if err := migrations[i].Down(db); err != nil {
migrationOptFunc := func(opt *migrationOptions) {
opt.isDryRun = isDryRun
}
if err := migrations[i].Down(db, migrationOptFunc); err != nil {
return err
}
}
Expand Down
44 changes: 35 additions & 9 deletions migration.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,16 @@ type MigrationRecord struct {
IsApplied bool // was this a result of up() or down()
}

type migrationOptions struct {
isDryRun bool
}

type MigrationOptionsFunc func(o *migrationOptions)

func MigrationWithDryRun() MigrationOptionsFunc {
return func(mo *migrationOptions) { mo.isDryRun = true }
}

// Migration struct.
type Migration struct {
Version int64
Expand All @@ -38,24 +48,32 @@ func (m *Migration) String() string {
}

// Up runs an up migration.
func (m *Migration) Up(db *sql.DB) error {
func (m *Migration) Up(db *sql.DB, opts ...MigrationOptionsFunc) error {
ctx := context.Background()
if err := m.run(ctx, db, true); err != nil {
option := &migrationOptions{}
for _, f := range opts {
f(option)
}
if err := m.run(ctx, db, true /* direction */, option.isDryRun); err != nil {
return err
}
return nil
}

// Down runs a down migration.
func (m *Migration) Down(db *sql.DB) error {
func (m *Migration) Down(db *sql.DB, opts ...MigrationOptionsFunc) error {
ctx := context.Background()
if err := m.run(ctx, db, false); err != nil {
option := &migrationOptions{}
for _, f := range opts {
f(option)
}
if err := m.run(ctx, db, false /* direction */, option.isDryRun); err != nil {
return err
}
return nil
}

func (m *Migration) run(ctx context.Context, db *sql.DB, direction bool) error {
func (m *Migration) run(ctx context.Context, db *sql.DB, direction bool, isDryRun bool) error {
switch filepath.Ext(m.Source) {
case ".sql":
f, err := baseFS.Open(m.Source)
Expand All @@ -69,22 +87,30 @@ func (m *Migration) run(ctx context.Context, db *sql.DB, direction bool) error {
return fmt.Errorf("ERROR %v: failed to parse SQL migration file: %w", filepath.Base(m.Source), err)
}

if isDryRun {
log.Printf("DRY-RUN %s\n", filepath.Base(m.Source))
return nil
}
start := time.Now()
if err := runSQLMigration(ctx, db, statements, useTx, m.Version, direction, m.noVersioning); err != nil {
return fmt.Errorf("ERROR %v: failed to run SQL migration: %w", filepath.Base(m.Source), err)
}
finish := truncateDuration(time.Since(start))

if len(statements) > 0 {
log.Printf("OK %s (%s)\n", filepath.Base(m.Source), finish)
log.Printf("OK %s (%s)\n", filepath.Base(m.Source), finish)
} else {
log.Printf("EMPTY %s (%s)\n", filepath.Base(m.Source), finish)
log.Printf("EMPTY %s (%s)\n", filepath.Base(m.Source), finish)
}

case ".go":
if !m.Registered {
return fmt.Errorf("ERROR %v: failed to run Go migration: Go functions must be registered and built into a custom binary (see https://github.com/pressly/goose/tree/master/examples/go-migrations)", m.Source)
}
if isDryRun {
log.Printf("DRY-RUN %s (%s)\n", filepath.Base(m.Source))
return nil
}
start := time.Now()
var empty bool
if m.UseTx {
Expand Down Expand Up @@ -124,9 +150,9 @@ func (m *Migration) run(ctx context.Context, db *sql.DB, direction bool) error {
}
finish := truncateDuration(time.Since(start))
if !empty {
log.Printf("OK %s (%s)\n", filepath.Base(m.Source), finish)
log.Printf("OK %s (%s)\n", filepath.Base(m.Source), finish)
} else {
log.Printf("EMPTY %s (%s)\n", filepath.Base(m.Source), finish)
log.Printf("EMPTY %s (%s)\n", filepath.Base(m.Source), finish)
}
}
return nil
Expand Down
4 changes: 2 additions & 2 deletions redo.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,10 @@ func Redo(db *sql.DB, dir string, opts ...OptionsFunc) error {
}
current.noVersioning = option.noVersioning

if err := current.Down(db); err != nil {
if err := current.Down(db, option.toMigrationOptionsFunc()); err != nil {
return err
}
if err := current.Up(db); err != nil {
if err := current.Up(db, option.toMigrationOptionsFunc()); err != nil {
return err
}
return nil
Expand Down
2 changes: 1 addition & 1 deletion reset.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ func Reset(db *sql.DB, dir string, opts ...OptionsFunc) error {
if !statuses[migration.Version] {
continue
}
if err = migration.Down(db); err != nil {
if err = migration.Down(db, option.toMigrationOptionsFunc()); err != nil {
return fmt.Errorf("failed to db-down: %w", err)
}
}
Expand Down
Loading