defmodule Ecto.Adapters.Mnesia.Migration do @moduledoc """ Functions for dealing with schema migrations """ alias Ecto.Adapters.Mnesia.Constraint alias Ecto.Adapters.Mnesia.Source @type t :: {module(), Keyword.t()} @type table() :: atom() @type access_opt() :: {:access_mode, :read_write | :read_only} @type disc_copies_opt() :: {:disc_copies, [node()]} @type disc_only_copies_opt() :: {:disc_only_copies, [node()]} @type index_opt() :: {:index, [atom()]} @type load_order_opt() :: {:load_order, integer()} @type majority_opt :: {:majority, boolean()} @type ram_copies_opt :: {:ram_copies, [node()]} @type storage_properties_opt :: {:storage_properties, [{atom(), term()}]} @type local_content_opt :: {:local_content, boolean()} @type create_opt() :: access_opt() | disc_copies_opt() | disc_only_copies_opt() | index_opt() | load_order_opt() | majority_opt() | ram_copies_opt() | storage_properties_opt() | local_content_opt() @type create_opts() :: [create_opt()] @type sync_create_opts() :: [{:timeout, integer()} | create_opts()] @doc """ Creates mnesia table. See `http://erlang.org/doc/man/mnesia.html#create_table-2` for options, except from the following ones: * `attributes`: ignored, computed from schema * `index`: in addition to primary keys indices * `record_name`: ignored, computed from schema * `snmp`: unsupported * `type`: ignored, all tables are of type `set` Returns created table name """ @spec create_table(module(), module(), create_opts()) :: {:ok, table()} | :ignore | {:error, term()} def create_table(repo, schema, opts \\ []) when is_list(opts) do repo.checkout(fn -> source = Source.new(%{schema: schema}) opts = build_options(source, opts) case :mnesia.create_table(source.table, opts) do {:atomic, :ok} -> {:ok, source.table} {:aborted, {:already_exists, _}} -> :ignore {:aborted, error} -> {:error, error} end end) end @doc """ Returns true if table is stored in RAM """ def ram_storage?({_, opts}) do case Keyword.get(opts, :ram_copies, []) do [] -> false _ -> true end end @doc """ Creates table and wait for its creation """ @spec sync_create_table(module(), module(), sync_create_opts()) :: :ok | {:error, term()} def sync_create_table(repo, schema, opts \\ []) when is_list(opts) do timeout = Keyword.get(opts, :timeout, 5_000) case create_table(repo, schema, opts) do {:ok, table} -> wait_for_table(table, timeout) :ignore -> :ok {:error, reason} -> {:error, reason} end end @doc """ Drop table Returns ok if table has been deleted or did not exist """ @spec drop_table(module(), module()) :: :ok | {:error, term()} def drop_table(repo, schema) do repo.checkout(fn -> %{table: table} = Source.new(%{schema: schema}) table |> :mnesia.delete_table() |> case do {:atomic, :ok} -> :ok {:aborted, {:no_exists, ^table}} -> :ok {:aborted, reason} -> {:error, reason} end end) end @doc """ Add a foreign key constraint """ @spec references(module(), atom(), Constraint.ForeignKey.opts()) :: :ok | {:error, term()} def references(from, relation, opts \\ []) do %{schema: from} |> Source.new() |> Constraint.ForeignKey.new(relation, opts) |> case do %Constraint.ForeignKey{errors: []} = c -> Constraint.register(c) c -> {:error, c.errors} end end @doc false def build_options(source, opts) do extra_keys = case source.extra_key do nil -> [] extra -> Map.keys(extra) end index = opts |> Keyword.get(:index, []) |> MapSet.new() |> MapSet.union(MapSet.new(extra_keys)) |> MapSet.to_list() [ index: index, attributes: source.attributes, type: :ordered_set, record_name: source.record_name ] |> Keyword.merge( Keyword.take(opts, [ :access_mode, :disc_copies, :disc_copies_only, :load_order, :majority, :ram_copies, :storage_properties, :local_content, :type ]) ) end defp wait_for_table(table, timeout) do case :mnesia.wait_for_tables([table], timeout) do :ok -> :ok {:error, reason} -> {:error, reason} {:timeout, [table]} -> {:error, {:timeout, table}} end end end