Packages
ecto
1.1.0
3.14.1
3.14.0
3.13.6
3.13.5
3.13.4
3.13.3
3.13.2
3.13.1
3.13.0
3.12.6
3.12.5
3.12.4
3.12.3
3.12.2
3.12.1
3.12.0
3.11.2
3.11.1
3.11.0
3.10.3
3.10.2
3.10.1
3.10.0
3.9.6
3.9.5
3.9.4
3.9.3
3.9.2
3.9.1
3.9.0
3.8.4
3.8.3
3.8.2
3.8.1
3.8.0
3.7.2
3.7.1
3.7.0
3.6.2
3.6.1
3.6.0
3.5.8
3.5.7
3.5.6
3.5.5
3.5.4
3.5.3
3.5.2
3.5.1
3.5.0
3.5.0-rc.1
3.5.0-rc.0
3.4.6
3.4.5
3.4.4
3.4.3
3.4.2
3.4.1
3.4.0
3.3.4
3.3.3
3.3.2
3.3.1
3.3.0
3.2.5
3.2.4
3.2.3
3.2.2
3.2.1
3.2.0
3.1.7
3.1.6
3.1.5
3.1.4
3.1.3
3.1.2
3.1.1
3.1.0
3.0.9
3.0.8
3.0.7
3.0.6
3.0.5
3.0.4
3.0.3
3.0.2
3.0.1
3.0.0
3.0.0-rc.1
3.0.0-rc.0
2.2.12
2.2.11
2.2.10
2.2.9
2.2.8
2.2.7
2.2.6
2.2.5
2.2.4
2.2.3
2.2.2
2.2.1
2.2.0
2.2.0-rc.1
2.2.0-rc.0
2.1.6
2.1.5
2.1.4
2.1.3
2.1.2
2.1.1
2.1.0
2.1.0-rc.5
2.1.0-rc.4
2.1.0-rc.3
2.1.0-rc.2
2.1.0-rc.1
2.1.0-rc.0
2.0.6
2.0.5
2.0.4
2.0.3
2.0.2
2.0.1
2.0.0
2.0.0-rc.6
2.0.0-rc.5
2.0.0-rc.4
2.0.0-rc.3
2.0.0-rc.2
2.0.0-rc.1
2.0.0-rc.0
2.0.0-beta.2
2.0.0-beta.1
2.0.0-beta.0
1.1.9
1.1.8
1.1.7
1.1.6
1.1.5
1.1.4
1.1.3
1.1.2
1.1.1
1.1.0
1.0.7
1.0.6
1.0.5
1.0.4
1.0.3
1.0.2
1.0.1
1.0.0
0.16.0
0.15.0
0.14.3
0.14.2
0.14.1
0.14.0
0.13.1
0.13.0
0.12.1
0.12.0
0.12.0-rc
0.11.3
0.11.2
0.11.1
0.11.0
0.10.3
0.10.2
0.10.1
0.10.0
0.9.0
0.8.1
0.8.0
0.7.2
0.7.1
0.7.0
0.6.0
0.5.1
0.5.0
0.4.0
0.3.0
0.2.8
0.2.7
0.2.6
0.2.5
0.2.4
0.2.3
0.2.2
0.2.1
0.2.0
0.1.0
A toolkit for data mapping and language integrated query for Elixir
Current section
Files
Jump to
Current section
Files
lib/ecto/migration/runner.ex
defmodule Ecto.Migration.Runner do
# A GenServer responsible for running migrations
# in either `:forward` or `:backward` directions.
@moduledoc false
use GenServer
require Logger
alias Ecto.Migration.Table
alias Ecto.Migration.Index
@opts [timeout: :infinity, log: false]
@doc """
Runs the given migration.
"""
def run(repo, module, direction, operation, migrator_direction, opts) do
level = Keyword.get(opts, :log, :info)
args = [self, repo, direction, migrator_direction, level]
{:ok, runner} = Supervisor.start_child(Ecto.Migration.Supervisor, args)
Process.put(:ecto_migration, %{runner: runner, prefix: opts[:prefix]})
log(level, "== Running #{inspect module}.#{operation}/0 #{direction}")
{time1, _} = :timer.tc(module, operation, [])
{time2, _} = :timer.tc(&flush/0, [])
time = time1 + time2
log(level, "== Migrated in #{inspect(div(time, 10000) / 10)}s")
stop()
end
@doc """
Starts the runner for the specified repo.
"""
def start_link(parent, repo, direction, migrator_direction, level) do
Agent.start_link(fn ->
Process.link(parent)
%{direction: direction, repo: repo, migrator_direction: migrator_direction,
command: nil, subcommands: [], level: level, commands: []}
end)
end
@doc """
Stops the runner.
"""
def stop() do
Agent.stop(runner())
end
@doc """
Returns the migrator command (up or down).
* forward + up: up
* forward + down: down
* forward + change: up
* backward + change: down
"""
def migrator_direction do
Agent.get(runner(), & &1.migrator_direction)
end
@doc """
Gets the prefix for this migration
"""
def prefix do
case Process.get(:ecto_migration) do
%{prefix: prefix} -> prefix
_ -> raise "could not find migration runner process for #{inspect self}"
end
end
@doc """
Executes queue migration commands.
Reverses the order commands are executed when doing a rollback
on a change/0 function and resets commands queue.
"""
def flush do
%{commands: commands, direction: direction} = Agent.get_and_update(runner(), fn (state) ->
{state, %{state | commands: []}}
end)
commands = if direction == :backward, do: commands, else: Enum.reverse(commands)
for command <- commands do
{repo, direction, level} = repo_and_direction_and_level()
execute_in_direction(repo, direction, level, command)
end
end
@doc """
Queues command tuples or strings for execution.
Ecto.MigrationError will be raised when the server
is in `:backward` direction and `command` is irreversible.
"""
def execute(command) do
Agent.update runner(), fn state ->
%{state | command: nil, subcommands: [], commands: [command|state.commands]}
end
end
@doc """
Starts a command.
"""
def start_command(command) do
Agent.update runner(), &put_in(&1.command, command)
end
@doc """
Queues and clears current command. Must call `start_command/1` first.
"""
def end_command do
Agent.update runner(), fn state ->
{operation, object} = state.command
command = {operation, object, Enum.reverse(state.subcommands)}
%{state | command: nil, subcommands: [], commands: [command|state.commands]}
end
end
@doc """
Adds a subcommand to the current command. Must call `start_command/1` first.
"""
def subcommand(subcommand) do
reply =
Agent.get_and_update(runner(), fn
%{command: nil} = state ->
{:error, state}
state ->
{:ok, update_in(state.subcommands, &[subcommand|&1])}
end)
case reply do
:ok ->
:ok
:error ->
raise Ecto.MigrationError, message: "cannot execute command outside of block"
end
end
## Execute
@creates [:create, :create_if_not_exists]
defp execute_in_direction(repo, :forward, level, command) do
log_and_execute_ddl(repo, level, command)
end
defp execute_in_direction(repo, :backward, level, {command, %Index{}=index}) when command in @creates do
log_and_execute_ddl(repo, level, {:drop, index})
end
defp execute_in_direction(repo, :backward, level, {:drop, %Index{}=index}) do
log_and_execute_ddl(repo, level, {:create, index})
end
defp execute_in_direction(repo, :backward, level, command) do
if reversed = reverse(command) do
log_and_execute_ddl(repo, level, reversed)
else
raise Ecto.MigrationError, message:
"cannot reverse migration command: #{command command}. " <>
"You will need to explicitly define up/1 and down/1 in your migration"
end
end
defp reverse({command, %Table{}=table, _columns}) when command in @creates,
do: {:drop, table}
defp reverse({:alter, %Table{}=table, changes}) do
if reversed = table_reverse(changes) do
{:alter, table, reversed}
end
end
defp reverse({:rename, %Table{}=table_current, %Table{}=table_new}),
do: {:rename, table_new, table_current}
defp reverse({:rename, %Table{}=table, current_column, new_column}),
do: {:rename, table, new_column, current_column}
defp reverse(_command), do: false
defp table_reverse([]), do: []
defp table_reverse([h|t]) do
if reversed = table_reverse(h) do
[reversed|table_reverse(t)]
end
end
defp table_reverse({:add, name, _type, _opts}), do: {:remove, name}
defp table_reverse(_), do: false
## Helpers
defp runner do
case Process.get(:ecto_migration) do
%{runner: runner} -> runner
_ -> raise "could not find migration runner process for #{inspect self}"
end
end
defp repo_and_direction_and_level do
Agent.get(runner(), fn %{repo: repo, direction: direction, level: level} ->
{repo, direction, level}
end)
end
defp log_and_execute_ddl(repo, level, command) do
log(level, command(command))
repo.__adapter__.execute_ddl(repo, command, @opts)
end
defp log(false, _msg), do: :ok
defp log(level, msg), do: Logger.log(level, msg)
defp command(ddl) when is_binary(ddl) or is_list(ddl),
do: "execute #{inspect ddl}"
defp command({:create, %Table{} = table, _}),
do: "create table #{quote_table(table.prefix, table.name)}"
defp command({:create_if_not_exists, %Table{} = table, _}),
do: "create table if not exists #{quote_table(table.prefix, table.name)}"
defp command({:alter, %Table{} = table, _}),
do: "alter table #{quote_table(table.prefix, table.name)}"
defp command({:drop, %Table{} = table}),
do: "drop table #{quote_table(table.prefix, table.name)}"
defp command({:drop_if_exists, %Table{} = table}),
do: "drop table if exists #{quote_table(table.prefix, table.name)}"
defp command({:create, %Index{} = index}),
do: "create index #{quote_table(index.prefix, index.name)}"
defp command({:create_if_not_exists, %Index{} = index}),
do: "create index if not exists #{quote_table(index.prefix, index.name)}"
defp command({:drop, %Index{} = index}),
do: "drop index #{quote_table(index.prefix, index.name)}"
defp command({:drop_if_exists, %Index{} = index}),
do: "drop index if exists #{quote_table(index.prefix, index.name)}"
defp command({:rename, %Table{} = current_table, %Table{} = new_table}),
do: "rename table #{quote_table(current_table.prefix, current_table.name)} to #{quote_table(new_table.prefix, new_table.name)}"
defp command({:rename, %Table{} = table, current_column, new_column}),
do: "rename column #{current_column} to #{new_column} on table #{quote_table(table.prefix, table.name)}"
defp quote_table(nil, name), do: quote_table(name)
defp quote_table(prefix, name), do: quote_table(prefix) <> "." <> quote_table(name)
defp quote_table(name) when is_atom(name),
do: quote_table(Atom.to_string(name))
defp quote_table(name), do: name
end