Ecto nested transactions - not clear

Hello,

the docs for Ecto.Repo.transaction/2 read:

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.

However, the What’s new in Ecto ebook states that:

(…) The snippet above starts a transaction and then calls transfer_money/3 that also runs in a transaction. This works because Ecto converts nested transaction into savepoints. In case an inner transaction fails, it rolls back to its specific savepoint.

Which one is true?

It’s not clear to me what’s the behaviour of nested transactions, and what happens to the parent transaction when a nested one fails.

Thanks!

2 Likes

:wave:

Given that those docs were written 3 years ago I’d bet that the what’s new in ecto book is right - good catch!

However, I can’t find it - if I didn’t take a wrong turn then transaction eventually ends up in DBConnection.transaction which in case of a nested transaction calls DBConnection.transaction_nested which doesn’t seem to make use of savepoints unless I’m missing something.

I’m gonna @michalmuskala as he probably knows/can shed some light on this. I’m really interested in this, as nested transactions are quite tricky but much too my delight both options that are presented here are much better than what ActiveRecord does by default if I understand them correctly :slight_smile:

2 Likes

The docs are correct here, we flatten all the transactions. Once anything fails in a transaction, the transaction is tainted and any subsequent operations will also fail - the only thing you can do is roll it back.

The book is incorrect here. @josevalim I think you have the power to correct it, right?

7 Likes

Yup, will do, thanks. Although I am not quite sure when a new edition will be out.

2 Likes

So what I can do if I want nested transation rollback only?

@sonhgc00016 The question then becomes “How would you do that in SQL?” as Ecto is just a fairly thin wrapper over SQL itself. So how would you do it in SQL and we can translate it to Ecto for you. At the basics you can just call the appropriate SQL directly in any case.

In my case. I have a long long logic in a function includes rollback and I’m using this function in nested transaction and I use the result of nested transaction and some other logic as summary result for wrapper transaction. So I has been used IO.inspect for the last result of wrapper transaction. It display as I expected but the wrapped transaction return tuple with result {:error, :rollback}. Can you explain for this?

Some invariant wasn’t fulfilled. Was it Ecto returning it specifically as it doesn’t allow for nested transactions (as not all, in fact most databases don’t support them), in which case calling the SQL straight will work, or was it returned by the database itself because it failed somehow?