lib/ecto/adapters/mnesia.ex

defmodule EctoQLC.Adapters.Mnesia do
  @moduledoc """
  Adapter module for [Mnesia](https://www.erlang.org/doc/man/mnesia.html).

  ## Clustering

  There is currently no support for automatic clustering or unsplit. If you decide to deploy EctoQLC.Adapters.Mnesia in a cluster you might find the following resources useful:

  * https://www.erlang.org/doc/apps/mnesia/mnesia_chap5#distribution-and-fault-tolerance
  * https://github.com/danschultzer/pow/blob/master/lib/pow/store/backend/mnesia_cache.ex
  * https://github.com/danschultzer/pow/blob/master/lib/pow/store/backend/mnesia_cache/unsplit.ex
  * https://github.com/uwiger/unsplit
  """
  use EctoQLC.Adapters.QLC, driver: :mnesia

  @impl Ecto.Adapter.Storage
  def storage_down(_opts) do
    with :stopped <- :mnesia.stop(),
         :ok <- :mnesia.delete_schema([node()]) do
         :ok
    else
      {:error, reason} -> raise RuntimeError, :mnesia.error_description(reason)
    end
  end

  @impl Ecto.Adapter.Storage
  def storage_status(_opts) do
    case :mnesia.system_info(:is_running) do
      :no -> :down
      :yes -> :up
      value -> {:error, value}
    end
  end

  @impl Ecto.Adapter.Storage
  def storage_up(_opts) do
    with :stopped <- :mnesia.stop,
      :ok <- :mnesia.create_schema([node()]) do
      :ok
    else
      {:aborted, {:already_exists, _table, _node, _storage_type}} -> {:error, :already_up}

      {:error,  {_, {:already_exists, _}}} -> {:error, :already_up}

      {:error, reason} -> raise RuntimeError, :mnesia.error_description(reason)
    end
  end

  @impl Ecto.Adapter.Transaction
  def transaction(_adapter_meta, _opts, fun) do
    # mnesia transaction support functions with args
    # with would allow it to forward options if needed.
    case :mnesia.transaction(fun) do
      {:aborted, reason} -> {:error, reason}
      {:atomic, result} -> {:ok, result}
      result -> {:ok, result}
    end
  end

  @impl Ecto.Adapter.Transaction
  def in_transaction?(_adapter_meta), do: :mnesia.is_transaction()

  @impl Ecto.Adapter.Transaction
  def rollback(adapter_meta, %_schema{} = value) do
    if in_transaction?(adapter_meta) do
      throw(:mnesia.abort(value))
    else
      raise "cannot call rollback outside of transaction"
    end
  end
end