lib/ecto/adapters/mnesia/constraint.ex

defmodule Ecto.Adapters.Mnesia.Constraint do
  @moduledoc """
  Functions for checking records against constraints
  """
  alias Ecto.Adapters.Mnesia.Constraint.Proto
  alias Ecto.Adapters.Mnesia.Source

  import Record

  defrecord :mnesia_constraints, table: nil, constraint: nil

  @table :mnesia_constraints

  @type constraint() :: {:unique | :foreign_key | :exclusion | :check, String.t()}

  @doc """
  Check parameters against constraints.

  Returns eventually violated constraints
  """
  @spec check(Source.t(), Keyword.t()) :: [constraint()]
  def check(source, params) do
    source.table
    |> get()
    |> Enum.reduce([], fn mnesia_constraints(constraint: c), acc ->
      case Proto.check(c, params) do
        :ok -> acc
        {:error, reason} -> [reason | acc]
      end
    end)
  end

  @doc """
  Returns constraints for the given table
  """
  def get(table) do
    :mnesia.transaction(fn ->
      :mnesia.read(@table, table, :read)
    end)
    |> case do
      {:atomic, c} -> c
    end
  end

  @doc """
  Register a constraint
  """
  def register(constraint) do
    :mnesia.transaction(fn ->
      :mnesia.write(mnesia_constraints(table: Proto.table(constraint), constraint: constraint))
    end)
    |> case do
      {:atomic, c} -> c
    end
  end

  @doc """
  Ensure constraints table has been created
  """
  def ensure_table(nodes) do
    case :mnesia.create_table(@table,
           disc_copies: nodes,
           attributes: [:table, :constraint],
           type: :bag,
           storage_properties: [dets: [auto_save: 5_000]],
           load_order: 100
         ) do
      {:atomic, :ok} ->
        :mnesia.wait_for_tables([@table], 1_000)

      {:aborted, {:already_exists, @table}} ->
        :mnesia.wait_for_tables([@table], 1_000)
    end
  end
end