How to rollback a specific ecto migration?

Is there a way to rollback a specific migration and only that one (“skipping” all the other ones)?

Would

mix ecto.rollback -v 20080906120000

accomplish that?

I believe you can’t. Here are all the available options as per mix help ecto.rollback:

## Command line options

  • -r, --repo - the repo to rollback
  • --all - revert all applied migrations
  • --step / -n - revert n number of applied migrations
  • --to / -v - revert all migrations down to and including version
  • --quiet - do not log migration commands
  • --prefix - the prefix to run migrations on
  • --pool-size - the pool size if the repository is started only for the
    task (defaults to 1)

-v reverts all down to a version, not a specific version.

I can also see why this is hard (if not impossible) to implement. The essential idea of having migrations is to have a consistent, versioned, and repeatable scripts to construct your DB. In constructing it, there are easily revertible operations such as creating tables, but also “weird” ones such as creating triggers or adding columns.

Suppose it’s possible: say migration 1 creates a table A, and migration 2 adds a column to that table. You then rollback migration 1, effectively deleting table A. What would happen to migration 2, should you try to rollback again later?

Technically it’s possible, but there will be so many edge cases that it’s a hell lot easier to just not allow that :slight_smile:

2 Likes

That makes sense. Just wanted to confirm it.

Thank you

You can always make a new migration that just undoes what a prior one did, but still keeps a proper versioned state.

8 Likes

It’s not pretty but this works for me:

def rollback(version) when is_integer(version) do
  re = ~r/^#{version}_.*\.exs/
  path = Application.app_dir(:your_app, Path.join(["priv", "repo", "migrations"]))

  with {:find, "" <> file} <- {:find, Enum.find(File.ls!(path), &String.match?(&1, re))},
       {:compile, [{mod, _} | _]} <- {:compile, Code.compile_file(Path.join(path, file))},
       {:rollback, :ok} <- {:rollback, Ecto.Migrator.down(Repo, version, mod)} do
    {:ok, "Reversed migration: #{file}"}
  else
    {:find, _} -> {:error, "No migration found with version prefix: #{version}"}
    {:compile, e} -> {:error, "Problem compiling migration module: #{inspect(e)}"}
    {:rollback, e} -> {:error, "Problem reversing migration: #{inspect(e)}"}
    e -> {:error, "Something unexpected happened: #{inspect(e)}"}
  end
end

Here’s an adapted example from the Ecto.Migrator docs.

Ecto.Migrator.with_repo(your_repo, &Ecto.Migrator.run(&1, :down, to: version))