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 ability to specify rollback limit/amount and the target version to migrate to #256

Open
wants to merge 4 commits into
base: main
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
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,12 @@ Writing: ./db/schema.sql

Pending migrations are always applied in numerical order. However, dbmate does not prevent migrations from being applied out of order if they are committed independently (for example: if a developer has been working on a branch for a long time, and commits a migration which has a lower version number than other already-applied migrations, dbmate will simply apply the pending migration). See [#159](https://github.com/amacneil/dbmate/issues/159) for a more detailed explanation.

You can also specify a migration to up-to.

```sh
$ dbmate up 20151127184807
```

### Rolling Back Migrations

By default, dbmate doesn't know how to roll back a migration. In development, it's often useful to be able to revert your database to a previous state. To accomplish this, implement the `migrate:down` section:
Expand All @@ -308,6 +314,14 @@ Rolling back: 20151127184807_create_users_table.sql
Writing: ./db/schema.sql
```

You can also rollback to a specific migration.

```sh
$ dbmate rollback 20151127184807
# or, with a limit option
$ dbmate rollback -limit 2 # will rollback the last two migrations
```

### Migration Options

dbmate supports options passed to a migration block in the form of `key:value` pairs. List of supported options:
Expand Down
12 changes: 11 additions & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ func NewApp() *cli.App {
},
},
Action: action(func(db *dbmate.DB, c *cli.Context) error {
db.TargetVersion = c.Args().First()
db.Verbose = c.Bool("verbose")
return db.CreateAndMigrate()
}),
Expand All @@ -129,7 +130,7 @@ func NewApp() *cli.App {
},
{
Name: "migrate",
Usage: "Migrate to the latest version",
Usage: "Migrate to the specified or latest version",
Flags: []cli.Flag{
&cli.BoolFlag{
Name: "verbose",
Expand All @@ -139,6 +140,7 @@ func NewApp() *cli.App {
},
},
Action: action(func(db *dbmate.DB, c *cli.Context) error {
db.TargetVersion = c.Args().First()
db.Verbose = c.Bool("verbose")
return db.Migrate()
}),
Expand All @@ -154,8 +156,16 @@ func NewApp() *cli.App {
EnvVars: []string{"DBMATE_VERBOSE"},
Usage: "print the result of each statement execution",
},
&cli.IntFlag{
Name: "limit",
Aliases: []string{"l"},
Usage: "Limits the amount of rollbacks (defaults to 1 if no target version is specified)",
Value: -1,
},
},
Action: action(func(db *dbmate.DB, c *cli.Context) error {
db.TargetVersion = c.Args().First()
db.Limit = c.Int("limit")
db.Verbose = c.Bool("verbose")
return db.Rollback()
}),
Expand Down
105 changes: 71 additions & 34 deletions pkg/dbmate/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ type DB struct {
WaitBefore bool
WaitInterval time.Duration
WaitTimeout time.Duration
Limit int
TargetVersion string
Log io.Writer
}

Expand All @@ -64,6 +66,8 @@ func New(databaseURL *url.URL) *DB {
WaitBefore: false,
WaitInterval: DefaultWaitInterval,
WaitTimeout: DefaultWaitTimeout,
Limit: -1,
TargetVersion: "",
Log: os.Stdout,
}
}
Expand Down Expand Up @@ -343,7 +347,7 @@ func (db *DB) migrate(drv Driver) error {

for _, filename := range files {
ver := migrationVersion(filename)
if ok := applied[ver]; ok {
if ok := applied[ver]; ok && ver != db.TargetVersion {
// migration already applied
continue
}
Expand Down Expand Up @@ -379,6 +383,11 @@ func (db *DB) migrate(drv Driver) error {
if err != nil {
return err
}

if ver == db.TargetVersion {
fmt.Fprintf(db.Log, "Reached target version %s\n", ver)
break
}
}

// automatically update schema file, silence errors
Expand Down Expand Up @@ -469,55 +478,83 @@ func (db *DB) Rollback() error {
}
defer dbutil.MustClose(sqlDB)

applied, err := drv.SelectMigrations(sqlDB, 1)
limit := db.Limit
// default limit is -1, if we don't specify a version it should only rollback one version, not all
if limit <= 0 && db.TargetVersion == "" {
limit = 1
}

applied, err := drv.SelectMigrations(sqlDB, limit)
if err != nil {
return err
}

// grab most recent applied migration (applied has len=1)
latest := ""
for ver := range applied {
latest = ver
}
if latest == "" {
return fmt.Errorf("can't rollback: no migrations have been applied")
if len(applied) == 0 {
return fmt.Errorf("can't rollback, no migrations found")
}

filename, err := findMigrationFile(db.MigrationsDir, latest)
if err != nil {
return err
var versions []string
for v := range applied {
versions = append(versions, v)
}

fmt.Fprintf(db.Log, "Rolling back: %s\n", filename)
// new → old
sort.Sort(sort.Reverse(sort.StringSlice(versions)))

_, down, err := parseMigration(filepath.Join(db.MigrationsDir, filename))
if err != nil {
return err
if db.TargetVersion != "" {
cache := map[string]bool{}
found := false

// latest version comes first, so take every version until the version matches
for _, ver := range versions {
if ver == db.TargetVersion {
found = true
break
}
cache[ver] = true
}
if !found {
return fmt.Errorf("target version not found")
}
applied = cache
}

execMigration := func(tx dbutil.Transaction) error {
// rollback migration
result, err := tx.Exec(down.Contents)
for version := range applied {
filename, err := findMigrationFile(db.MigrationsDir, version)
if err != nil {
return err
} else if db.Verbose {
db.printVerbose(result)
}

// remove migration record
return drv.DeleteMigration(tx, latest)
}
fmt.Fprintf(db.Log, "Rolling back: %s\n", filename)
_, down, err := parseMigration(filepath.Join(db.MigrationsDir, filename))
if err != nil {
return err
}

if down.Options.Transaction() {
// begin transaction
err = doTransaction(sqlDB, execMigration)
} else {
// run outside of transaction
err = execMigration(sqlDB)
}
execMigration := func(tx dbutil.Transaction) error {
// rollback migration
result, err := tx.Exec(down.Contents)
if err != nil {
return err
} else if db.Verbose {
db.printVerbose(result)
}

if err != nil {
return err
// remove migration record
return drv.DeleteMigration(tx, version)
}

if down.Options.Transaction() {
// begin transaction
err = doTransaction(sqlDB, execMigration)
} else {
// run outside of transaction
err = execMigration(sqlDB)
}

if err != nil {
return err
}
}

// automatically update schema file, silence errors
Expand Down Expand Up @@ -582,7 +619,7 @@ func (db *DB) CheckMigrationsStatus(drv Driver) ([]StatusResult, error) {
}
defer dbutil.MustClose(sqlDB)

applied, err := drv.SelectMigrations(sqlDB, -1)
applied, err := drv.SelectMigrations(sqlDB, db.Limit)
if err != nil {
return nil, err
}
Expand Down
Loading