lib/ecto_savepoint.ex

defmodule EctoSavepoint do
  @moduledoc """
  Simple Ecto extension to use savepoints. Works with both postgrex and myxql.

  **Disclaimer:** savepoints are not officially supported by Ecto for a reason. 
  If you run into unexpected behaviour, please report so these cases can be documented here.

  Add this line in your Repo:
      use EctoSavepoint

  And you can now do:
      MyRepo.transaction(fn ->
        MyRepo.insert(...)

        MyRepo.savepoint("my_savepoint")

        MyRepo.insert(...)

        MyRepo.rollback_to_savepoint("my_savepoint")

        MyRepo.insert(...)
      end)

  This way, the insertions between savepoint and rollback are rolled back.

  If you don't want to `use`, you can do the calls directly, as in: 
      EctoSavepoint.savepoint(MyRepo, "my_savepoint")
  """

  @doc """
  Creates a savepoint

  Should be called within a transaction
  """
  @spec savepoint(repo :: module, name :: binary | atom) :: term
  def savepoint(repo, name) do
    repo.query("SAVEPOINT #{name}")
  end

  @doc """
  Creates a savepoint

  Should be called within the same transaction that created the savepoint
  """
  @spec rollback_to_savepoint(repo :: module, name :: binary | atom) :: term
  def rollback_to_savepoint(repo, name) do
    repo.query("ROLLBACK TO SAVEPOINT #{name}")
  end

  defmacro __using__(_opts) do
    quote do
      def savepoint(name), do: EctoSavepoint.savepoint(get_dynamic_repo(), name)
      def rollback_to_savepoint(name), do: EctoSavepoint.rollback_to_savepoint(get_dynamic_repo(), name)
    end
  end
end