At the time an outer Atomic
calls connection.commit()
, it has not yet set the connection back into autocommit mode (that happens in a finally
block at the end of Atomic.__exit__
. Thus, if database queries are executed in an on-commit hook, they will open a new implicit transaction (since autocommit is still off), and then when Atomic
does attempt to restore autocommit, it will fail on Postgres with "autocommit cannot be used inside a transaction."
On SQLite, which always autocommits, or MySQL, which apparently implicitly commits the implicit transaction when asked to go back to autocommit mode, this is not a problem.
I can think of two solutions:
- Have
run_and_clear_commit_hooks
set autocommit on, then run the hooks, then restore autocommit to whatever it was previously set to. This has the disadvantage that in the typical success case, after the transaction is committed, autocommit will be turned on, then back off, then back on again.
- Add a
run_commit_hooks_on_set_autocommit_on
flag to the connection. Instead of running the hooks in commit()
, just have commit()
set this flag to True
, and if this flag is True, run the hooks the next time set_autocommit(True)
is called (and reset the flag). (For SQLite, we will still run the hooks directly in commit()
, since Atomic
never calls set_autocommit(True)
). This has the downside that it's depending rather more heavily than I like on details of the implementation of Atomic
. I think this is ok (I've documented that django-transaction-hooks
is only tested to work with autocommit mode and transaction.atomic
), but I don't love it.
(If transaction-hooks become part of core, it would also be possible to have Atomic
explicitly notify the connection after successful commit and full reset back to autocommit mode. This is a variant of option (2) -- it means the on_commit
implementation is still dependent on Atomic
, but the dependence is a bit less muddy.)
I think of these options (2) is superior, but I'm still thinking about whether there are alternatives I've missed.