dbmate icon indicating copy to clipboard operation
dbmate copied to clipboard

Bug? Expected functionality? No error thrown on rollback if migration doesn't exist

Open alex-hall opened this issue 5 months ago • 2 comments

Description

When attempting to rollback a migration whose file is not present in the current codebase, dbmate silently skips the missing migration and rolls back the previous available migration instead of throwing an error.

We encountered this when a migration was accidentally run against our staging database from a local machine. The staging environment didn't have the migration file, but when we attempted to rollback (from the staging app), dbmate reported success while actually rolling back the wrong migration.

While this scenario represents user error and isn't a standard workflow, the silent failure behavior is problematic because it provides misleading feedback and can cause unintended schema changes.

Version: 2.26.0 Database: Postgres Operating System: Heroku

Steps To Reproduce

  1. Apply migration A to database
  2. Apply migration B to database
  3. Remove migration B file from codebase (simulating missing migration file)
  4. Run dbmate rollback
  5. dbmate reports "Done." and rolls back migration A instead of migration B

Expected Behavior dbmate should throw an error when the most recent migration file cannot be found, such as: Error: Cannot rollback migration [filename] - migration file not found

Actual Behavior dbmate silently skips the missing migration file and rolls back the next available migration, reporting success while performing an unintended operation.

alex-hall avatar Aug 15 '25 18:08 alex-hall

Problem

When using --strict mode, dbmate should fail if a migration file is missing during rollback operations. Currently, it silently ignores missing files.

Proposed Solution

Add a file existence check when db.Strict is enabled before attempting to rollback.

Patch

diff --git a/pkg/dbmate/db.go b/pkg/dbmate/db.go
index fb55683..1234567 100644
--- a/pkg/dbmate/db.go
+++ b/pkg/dbmate/db.go
@@ -360,6 +360,15 @@ func (db *DB) Rollback() error {
 	if latest == nil {
 		return ErrNoRollback
 	}
+
+	// In strict mode, fail if the migration file is not found
+	if db.Strict {
+		_, err := latest.FS.Open(latest.FilePath)
+		if err != nil {
+			return fmt.Errorf("can't rollback migration %s in --strict mode: %w", latest.Version, ErrMigrationNotFound)
+		}
+	}
+
 	fmt.Fprintf(db.Log, "Rolling back: %s\n", latest.FileName)
 
 	start := time.Now()

alex-hall avatar Aug 15 '25 19:08 alex-hall

Is this a duplicate of #628? Not a duplicate, but a similar motivation or circumstance?

dossy avatar Aug 15 '25 21:08 dossy