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