# SPDX-License-Identifier: Apache-2.0
defmodule EctoSparkles.Migrator do
require Logger
def rollback(repo \\ nil, step \\ 1)
def migrate(repo) do
Logger.info("Migrate #{inspect(repo)}")
{:ok, _, _} = Ecto.Migrator.with_repo(repo, &Ecto.Migrator.run(&1, :up, all: true))
end
def rollback(repo, step) when not is_nil(repo) do
Logger.info("Rollback #{inspect(repo)} by #{inspect(step)} step")
{:ok, _, _} = Ecto.Migrator.with_repo(repo, &Ecto.Migrator.run(&1, :down, step: step))
end
def rollback_to(repo, version) do
Logger.info("Rollback #{inspect(repo)} to version #{inspect(version)}")
{:ok, _, _} = Ecto.Migrator.with_repo(repo, &Ecto.Migrator.run(&1, :down, to: version))
end
def rollback_all(repo) do
Logger.info("Rollback #{inspect(repo)}")
{:ok, _, _} = Ecto.Migrator.with_repo(repo, &Ecto.Migrator.run(&1, :down, all: true))
end
def create(repo, attempt \\ 0) do
try do
case repo.__adapter__.storage_up(repo.config) do
:ok ->
Logger.info("The database for #{inspect(repo)} has been created")
:ok
{:error, :already_up} ->
:ok
e ->
Logger.warn("The database for #{inspect(repo)} could not be created: #{inspect(e)}")
if attempt < 10 do
# wait for Postgres to be up
Process.sleep(1000)
create(repo, attempt + 1)
else
Logger.warn("After 10 attempts, the database for #{inspect(repo)} still could not be created: #{inspect(e)}")
end
end
rescue
e ->
Logger.error("The database for #{inspect(repo)} failed to be created: #{inspect(e)}")
end
end
def migrate do
for repo <- repos(), do: migrate(repo)
end
def rollback(nil, step) do
for repo <- repos(), do: rollback(repo, step)
end
def rollback_to(version) do
for repo <- repos(), do: rollback_to(repo, version)
end
def rollback_all() do
for repo <- repos(), do: rollback_all(repo)
end
def create() do
for repo <- repos(), do: create(repo)
end
defp repos do
app = Application.fetch_env!(:ecto_sparkles, :otp_app)
Application.load(app)
Application.fetch_env!(app, :ecto_repos)
end
@doc """
Print the migration status for configured Repos' migrations.
"""
def status do
for repo <- repos(), do: print_migrations_for(repo)
end
defp print_migrations_for(repo) do
paths =
repo_migrations_path(repo)
|> IO.inspect(label: "Migration path")
{:ok, repo_status, _} =
Ecto.Migrator.with_repo(repo, &Ecto.Migrator.migrations(&1, paths), mode: :temporary)
IO.puts(
"""
Repo: #{inspect(repo)}
Status Migration ID Migration Name
--------------------------------------------------
""" <>
Enum.map_join(repo_status, "\n", fn {status, number, description} ->
" #{pad(status, 10)}#{pad(number, 16)}#{description}"
end) <> "\n"
)
end
defp repo_migrations_path(repo) do
config = repo.config()
priv =
config[:priv] ||
"priv/#{repo |> Module.split() |> List.last() |> Macro.underscore()}"
(Keyword.get(config, :project_path) || Application.app_dir(Keyword.fetch!(config, :otp_app)))
|> Path.join(priv)
end
defp pad(content, pad) do
content
|> to_string
|> String.pad_trailing(pad)
end
end