Skip to content

Migrator records or deletes a migration version when the migration fails #749

Description

@lukaszsamson

Elixir version

any

Database and Version

any

Ecto Versions

3.14.0

Database Adapter and Versions (postgrex, myxql, etc)

any (Reproduced with `EctoSQL.TestAdapter)

Current behavior

If a migration module does not implement neither the requested callback nor change/0, Ecto.Migrator.up/4 and down/4 raise Ecto.MigrationError but still modify the schema_migrations table. A broken migration applied during tests/development corrupts the state and requires manual intervention.

Repro:
run a broken migration

defmodule InvalidMigration do
  use Ecto.Migration
end

assert_raise Ecto.MigrationError, fn ->
  Ecto.Migrator.up(TestRepo, 13, InvalidMigration, log: false)
end

# Incorrect: version 13 was recorded even though no migration ran.
assert Ecto.Migrator.migrated_versions(TestRepo) == [1, 2, 3, 13]

Reason:
do_up/5 and do_down/5 return {:error, %Ecto.MigrationError{}}. In async_migrate_maybe_in_transaction/7, fun_with_status unconditionally calls SchemaMigration.up/4 or SchemaMigration.down/4.

Secondary issue:

With strict_version_order: true, up/4 currently checks version ordering only after do_up/5 returns. An out-of-order migration is applied and its version is committed before Ecto.MigrationError is raised:

assert_raise Ecto.MigrationError, fn ->
  Ecto.Migrator.up(TestRepo, 0, ValidMigration,
    log: false,
    strict_version_order: true
  )
end

# Incorrect: migration 0 has already run and is recorded.
assert 0 in Ecto.Migrator.migrated_versions(TestRepo)

This contradicts the documented behavior that strict ordering should abort the migration.

Expected behavior

Broken migrations should not affect schema_migrations table. Retrying after fixing the module should execute the migration instead of returning :already_up or :already_down. With strict_version_order: true, version ordering should be validated before executing any migration commands

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions