diff --git a/README.md b/README.md index 826f2ce..b2cb010 100644 --- a/README.md +++ b/README.md @@ -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: @@ -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: diff --git a/pkg/dbmate/db_test.go b/pkg/dbmate/db_test.go index d2e42e5..8582a63 100644 --- a/pkg/dbmate/db_test.go +++ b/pkg/dbmate/db_test.go @@ -47,6 +47,8 @@ func TestNew(t *testing.T) { require.False(t, db.WaitBefore) require.Equal(t, time.Second, db.WaitInterval) require.Equal(t, 60*time.Second, db.WaitTimeout) + require.Equal(t, -1, db.Limit) + require.Equal(t, "", db.TargetVersion) } func TestGetDriver(t *testing.T) { @@ -242,9 +244,11 @@ func TestWaitBeforeVerbose(t *testing.T) { `Applying: 20151129054053_test_migration.sql Rows affected: 1 Applying: 20200227231541_test_posts.sql +Rows affected: 0 +Applying: 20220607110405_test_category.sql Rows affected: 0`) require.Contains(t, output, - `Rolling back: 20200227231541_test_posts.sql + `Rolling back: 20220607110405_test_category.sql Rows affected: 0`) } @@ -291,6 +295,37 @@ func TestMigrate(t *testing.T) { } } +func TestMigrateToTarget(t *testing.T) { + for _, u := range testURLs() { + t.Run(u.Scheme, func(t *testing.T) { + db := newTestDB(t, u) + db.TargetVersion = "20151129054053" + drv, err := db.GetDriver() + require.NoError(t, err) + + // drop and recreate database + err = db.Drop() + require.NoError(t, err) + err = db.Create() + require.NoError(t, err) + + // migrate + err = db.Migrate() + require.NoError(t, err) + + // verify results + sqlDB, err := drv.Open() + require.NoError(t, err) + defer dbutil.MustClose(sqlDB) + + count := 0 + err = sqlDB.QueryRow(`select count(*) from schema_migrations`).Scan(&count) + require.NoError(t, err) + require.Equal(t, 1, count) + }) + } +} + func TestUp(t *testing.T) { for _, u := range testURLs() { t.Run(u.Scheme, func(t *testing.T) { @@ -350,13 +385,59 @@ func TestRollback(t *testing.T) { require.NoError(t, err) require.Equal(t, 1, count) - err = sqlDB.QueryRow("select count(*) from posts").Scan(&count) + err = sqlDB.QueryRow("select count(*) from categories").Scan(&count) require.Nil(t, err) // rollback err = db.Rollback() require.NoError(t, err) + // verify rollback + err = sqlDB.QueryRow("select count(*) from schema_migrations").Scan(&count) + require.NoError(t, err) + require.Equal(t, 2, count) + + err = sqlDB.QueryRow("select count(*) from categories").Scan(&count) + require.NotNil(t, err) + require.Regexp(t, "(does not exist|doesn't exist|no such table)", err.Error()) + }) + } +} + +func TestRollbackToTarget(t *testing.T) { + for _, u := range testURLs() { + t.Run(u.Scheme, func(t *testing.T) { + db := newTestDB(t, u) + drv, err := db.GetDriver() + require.NoError(t, err) + + // drop, recreate, and migrate database + err = db.Drop() + require.NoError(t, err) + err = db.Create() + require.NoError(t, err) + err = db.Migrate() + require.NoError(t, err) + + // verify migration + sqlDB, err := drv.Open() + require.NoError(t, err) + defer dbutil.MustClose(sqlDB) + + count := 0 + err = sqlDB.QueryRow(`select count(*) from schema_migrations + where version = '20151129054053'`).Scan(&count) + require.NoError(t, err) + require.Equal(t, 1, count) + + err = sqlDB.QueryRow("select count(*) from categories").Scan(&count) + require.Nil(t, err) + + // rollback + db.TargetVersion = "20151129054053" + err = db.Rollback() + require.NoError(t, err) + // verify rollback err = sqlDB.QueryRow("select count(*) from schema_migrations").Scan(&count) require.NoError(t, err) @@ -365,6 +446,60 @@ func TestRollback(t *testing.T) { err = sqlDB.QueryRow("select count(*) from posts").Scan(&count) require.NotNil(t, err) require.Regexp(t, "(does not exist|doesn't exist|no such table)", err.Error()) + + err = sqlDB.QueryRow("select count(*) from categories").Scan(&count) + require.NotNil(t, err) + require.Regexp(t, "(does not exist|doesn't exist|no such table)", err.Error()) + }) + } +} + +func TestRollbackToLimit(t *testing.T) { + for _, u := range testURLs() { + t.Run(u.Scheme, func(t *testing.T) { + db := newTestDB(t, u) + drv, err := db.GetDriver() + require.NoError(t, err) + + // drop, recreate, and migrate database + err = db.Drop() + require.NoError(t, err) + err = db.Create() + require.NoError(t, err) + err = db.Migrate() + require.NoError(t, err) + + // verify migration + sqlDB, err := drv.Open() + require.NoError(t, err) + defer dbutil.MustClose(sqlDB) + + count := 0 + err = sqlDB.QueryRow(`select count(*) from schema_migrations + where version = '20151129054053'`).Scan(&count) + require.NoError(t, err) + require.Equal(t, 1, count) + + err = sqlDB.QueryRow("select count(*) from categories").Scan(&count) + require.Nil(t, err) + + // rollback + db.Limit = 2 + err = db.Rollback() + require.NoError(t, err) + + // verify rollback + err = sqlDB.QueryRow("select count(*) from schema_migrations").Scan(&count) + require.NoError(t, err) + require.Equal(t, 1, count) + + err = sqlDB.QueryRow("select count(*) from posts").Scan(&count) + require.NotNil(t, err) + require.Regexp(t, "(does not exist|doesn't exist|no such table)", err.Error()) + + err = sqlDB.QueryRow("select count(*) from categories").Scan(&count) + require.NotNil(t, err) + require.Regexp(t, "(does not exist|doesn't exist|no such table)", err.Error()) }) } } @@ -390,7 +525,7 @@ func TestStatus(t *testing.T) { // two pending results, err := db.CheckMigrationsStatus(drv) require.NoError(t, err) - require.Len(t, results, 2) + require.Len(t, results, 3) require.False(t, results[0].Applied) require.False(t, results[1].Applied) @@ -398,12 +533,13 @@ func TestStatus(t *testing.T) { err = db.Migrate() require.NoError(t, err) - // two applied + // three applied results, err = db.CheckMigrationsStatus(drv) require.NoError(t, err) - require.Len(t, results, 2) + require.Len(t, results, 3) require.True(t, results[0].Applied) require.True(t, results[1].Applied) + require.True(t, results[2].Applied) // rollback last migration err = db.Rollback() @@ -412,9 +548,10 @@ func TestStatus(t *testing.T) { // one applied, one pending results, err = db.CheckMigrationsStatus(drv) require.NoError(t, err) - require.Len(t, results, 2) + require.Len(t, results, 3) require.True(t, results[0].Applied) - require.False(t, results[1].Applied) + require.True(t, results[1].Applied) + require.False(t, results[2].Applied) }) } } diff --git a/testdata/db/migrations/20220607110405_test_category.sql b/testdata/db/migrations/20220607110405_test_category.sql new file mode 100644 index 0000000..098ff16 --- /dev/null +++ b/testdata/db/migrations/20220607110405_test_category.sql @@ -0,0 +1,9 @@ +-- migrate:up +create table categories ( + id integer, + title varchar(50), + slug varchar(100) +); + +-- migrate:down +drop table categories;