LoginSignup
5
1

More than 5 years have passed since last update.

Ecto.Repo.transaction/3 は入れ子にできる

Posted at

PostgreSQL でのみ確認。他は分からない。

公式ドキュメントに書いてあった...

If transaction/2 is called inside another transaction, the function is simply executed, without wrapping the new transaction call in any way. If there is an error in the inner transaction and the error is rescued, or the inner transaction is rolled back, the whole outer transaction is marked as tainted, guaranteeing nothing will be committed.

しかも、内側のトランザクションの失敗も考慮してくれるらしい。便利。

Repo.transaction(fn ->
  Repo.transaction(fn ->
    Ecto.Adapters.SQL.query!(Repo, "INSERT INTO ...")
    raise "ERROR!"
  end)
end)

このときの debug ログは以下:

05:19:47.008 [debug] QUERY OK db=0.1ms
begin []

05:19:47.009 [debug] QUERY OK db=0.4ms
INSERT INTO ...

05:19:47.010 [debug] QUERY OK db=0.4ms
rollback []
** (RuntimeError) ERROR!
    (stdlib) erl_eval.erl:675: :erl_eval.do_apply/6
    (ecto_sql) lib/ecto/adapters/sql.ex:787: anonymous fn/3 in Ecto.Adapters.SQL.checkout_or_transaction/4
    (db_connection) lib/db_connection.ex:734: DBConnection.transaction/3
    (ecto_sql) lib/ecto/adapters/sql.ex:787: anonymous fn/3 in Ecto.Adapters.SQL.checkout_or_transaction/4
    (db_connection) lib/db_connection.ex:1341: DBConnection.run_transaction/4

見ての通り、Ecto.Repo.transaction/2 を入れ子にしても、発行される BEGIN... は外側のひとつだけ。

Ecto.Multi で処理をまとめるときに、前段の処理結果を参照するために Multi の名前が決め打ちになるので悩んでたけど、複数の Multi に分割して、Ecto.Repo.transaction/3 の関数内で実行してやるので良さそう。

Repo.transaction(fn ->
  with {:ok, %{create_user: user}} <- Repo.transaction(create_user_multi),
       friend_requests_multi = build_friend_requests_with_user(user),
       {:ok, %{friend_requests: friend_requests} <- Repo.transaction(friend_requests_multi) do
    ...
  else
    error ->
      handle_transaction_error(error)
  end
end
5
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
1