Packages
ecto
2.0.0-beta.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/migrator.ex
defmodule Ecto.Migrator do
@moduledoc """
This module provides the migration API.
## Example
defmodule MyApp.MigrationExample do
use Ecto.Migration
def up do
execute "CREATE TABLE users(id serial PRIMARY_KEY, username text)"
end
def down do
execute "DROP TABLE users"
end
end
Ecto.Migrator.up(Repo, 20080906120000, MyApp.MigrationExample)
"""
require Logger
alias Ecto.Migration.Runner
alias Ecto.Migration.SchemaMigration
@doc """
Gets all migrated versions.
This function ensures the migration table exists
if no table has been defined yet.
## Options
* `:log` - the level to use for logging. Defaults to `:info`.
Can be any of `Logger.level/0` values or `false`.
* `:prefix` - the prefix to run the migrations on
"""
@spec migrated_versions(Ecto.Repo.t, Keyword.t) :: [integer]
def migrated_versions(repo, opts \\ []) do
SchemaMigration.ensure_schema_migrations_table!(repo, opts[:prefix])
SchemaMigration.migrated_versions(repo, opts[:prefix])
end
@doc """
Runs an up migration on the given repository.
## Options
* `:log` - the level to use for logging. Defaults to `:info`.
Can be any of `Logger.level/0` values or `false`.
* `:prefix` - the prefix to run the migrations on
"""
@spec up(Ecto.Repo.t, integer, Module.t, Keyword.t) :: :ok | :already_up | no_return
def up(repo, version, module, opts \\ []) do
versions = migrated_versions(repo, opts)
if version in versions do
:already_up
else
do_up(repo, version, module, opts)
:ok
end
end
defp do_up(repo, version, module, opts) do
run_maybe_in_transaction repo, module, fn ->
attempt(repo, module, :forward, :up, :up, opts)
|| attempt(repo, module, :forward, :change, :up, opts)
|| raise Ecto.MigrationError, message: "#{inspect module} does not implement a `up/0` or `change/0` function"
SchemaMigration.up(repo, version, opts[:prefix])
end
end
@doc """
Runs a down migration on the given repository.
## Options
* `:log` - the level to use for logging. Defaults to `:info`.
Can be any of `Logger.level/0` values or `false`.
"""
@spec down(Ecto.Repo.t, integer, Module.t) :: :ok | :already_down | no_return
def down(repo, version, module, opts \\ []) do
versions = migrated_versions(repo, opts)
if version in versions do
do_down(repo, version, module, opts)
:ok
else
:already_down
end
end
defp do_down(repo, version, module, opts) do
run_maybe_in_transaction repo, module, fn ->
attempt(repo, module, :forward, :down, :down, opts)
|| attempt(repo, module, :backward, :change, :down, opts)
|| raise Ecto.MigrationError, message: "#{inspect module} does not implement a `down/0` or `change/0` function"
SchemaMigration.down(repo, version, opts[:prefix])
end
end
defp run_maybe_in_transaction(repo, module, fun) do
cond do
module.__migration__[:disable_ddl_transaction] ->
fun.()
repo.__adapter__.supports_ddl_transaction? ->
repo.transaction [log: false, timeout: :infinity], fun
true ->
fun.()
end
end
defp attempt(repo, module, direction, operation, reference, opts) do
if Code.ensure_loaded?(module) and
function_exported?(module, operation, 0) do
Runner.run(repo, module, direction, operation, reference, opts)
:ok
end
end
@doc """
Apply migrations in a directory to a repository with given strategy.
A strategy must be given as an option.
## Options
* `:all` - runs all available if `true`
* `:step` - runs the specific number of migrations
* `:to` - runs all until the supplied version is reached
* `:log` - the level to use for logging. Defaults to `:info`.
Can be any of `Logger.level/0` values or `false`.
"""
@spec run(Ecto.Repo.t, binary, atom, Keyword.t) :: [integer]
def run(repo, directory, direction, opts) do
versions = migrated_versions(repo, opts)
cond do
opts[:all] ->
run_all(repo, versions, directory, direction, opts)
to = opts[:to] ->
run_to(repo, versions, directory, direction, to, opts)
step = opts[:step] ->
run_step(repo, versions, directory, direction, step, opts)
true ->
raise ArgumentError, message: "expected one of :all, :to, or :step strategies"
end
end
defp run_to(repo, versions, directory, direction, target, opts) do
within_target_version? = fn
{version, _, _}, target, :up ->
version <= target
{version, _, _}, target, :down ->
version >= target
end
pending_in_direction(versions, directory, direction)
|> Enum.take_while(&(within_target_version?.(&1, target, direction)))
|> migrate(direction, repo, opts)
end
defp run_step(repo, versions, directory, direction, count, opts) do
pending_in_direction(versions, directory, direction)
|> Enum.take(count)
|> migrate(direction, repo, opts)
end
defp run_all(repo, versions, directory, direction, opts) do
pending_in_direction(versions, directory, direction)
|> migrate(direction, repo, opts)
end
defp pending_in_direction(versions, directory, :up) do
migrations_for(directory)
|> Enum.filter(fn {version, _name, _file} -> not (version in versions) end)
end
defp pending_in_direction(versions, directory, :down) do
migrations_for(directory)
|> Enum.filter(fn {version, _name, _file} -> version in versions end)
|> Enum.reverse
end
defp migrations_for(directory) do
query = Path.join(directory, "*")
for entry <- Path.wildcard(query),
info = extract_migration_info(entry),
do: info
end
defp extract_migration_info(file) do
base = Path.basename(file)
ext = Path.extname(base)
case Integer.parse(Path.rootname(base)) do
{integer, "_" <> name} when ext == ".exs" ->
{integer, name, file}
_ ->
nil
end
end
defp migrate([], direction, _repo, opts) do
level = Keyword.get(opts, :log, :info)
log(level, "Already #{direction}")
[]
end
defp migrate(migrations, direction, repo, opts) do
ensure_no_duplication(migrations)
Enum.map migrations, fn {version, _name, file} ->
{mod, _bin} =
Enum.find(Code.load_file(file), fn {mod, _bin} ->
function_exported?(mod, :__migration__, 0)
end) || raise_no_migration_in_file(file)
case direction do
:up -> do_up(repo, version, mod, opts)
:down -> do_down(repo, version, mod, opts)
end
version
end
end
defp ensure_no_duplication([{version, name, _} | t]) do
if List.keyfind(t, version, 0) do
raise Ecto.MigrationError,
message: "migrations can't be executed, migration version #{version} is duplicated"
end
if List.keyfind(t, name, 1) do
raise Ecto.MigrationError,
message: "migrations can't be executed, migration name #{name} is duplicated"
end
ensure_no_duplication(t)
end
defp ensure_no_duplication([]), do: :ok
defp raise_no_migration_in_file(file) do
raise Ecto.MigrationError,
message: "file #{Path.relative_to_cwd(file)} does not contain any Ecto.Migration"
end
defp log(false, _msg), do: :ok
defp log(level, msg), do: Logger.log(level, msg)
end