Packages
ecto
0.14.3
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.
"""
@spec migrated_versions(Ecto.Repo.t) :: [integer]
def migrated_versions(repo) do
SchemaMigration.ensure_schema_migrations_table!(repo)
SchemaMigration.migrated_versions(repo)
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`.
"""
@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)
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)
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)
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)
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)
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(migrations, direction, repo, opts) do
if Enum.empty? migrations do
level = Keyword.get(opts, :log, :info)
log(level, "Already #{direction}")
end
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