How to do transactions, especially nested hga@xxxxxx (18 Sep 2019 01:22 UTC)
Re: How to do transactions, especially nested Peter Bex (18 Sep 2019 06:05 UTC)
Re: How to do transactions, especially nested Lassi Kortela (18 Sep 2019 07:41 UTC)
Re: How to do transactions, especially nested Peter Bex (18 Sep 2019 07:58 UTC)
Re: How to do transactions, especially nested Lassi Kortela (18 Sep 2019 08:26 UTC)
Re: How to do transactions, especially nested Alaric Snell-Pym (18 Sep 2019 10:27 UTC)
Re: How to do transactions, especially nested Alaric Snell-Pym (18 Sep 2019 10:26 UTC)
Re: How to do transactions, especially nested hga@xxxxxx (18 Sep 2019 15:54 UTC)

Re: How to do transactions, especially nested Peter Bex 18 Sep 2019 07:58 UTC
On Wed, Sep 18, 2019 at 10:41:05AM +0300, Lassi Kortela wrote:
> > Usually you'll only want to roll back when an exception occurs.  If you
> > want to explicitly roll back, you can just return #f from the thunk which
> > should be rolled back.
>
> Is returning true/#f a good interface in practice? I used that kind of
> interface in some other language (Python or Ruby perhaps) and it was
> difficult to track down bugs where you didn't realize that return value was
> significant.

Yeah, I find that part of the design a bit questionable as well, TBH.
I just didn't want to require the user throwing and catching an exception
just to abort the transaction.

Perhaps we can specify a specific exception type to throw which will just
return from the with-transaction but not propagate the exception to user
code?  That would probably be the better API, in hindsight.  Any other type
of exception will be re-raised, just not this one.

Alternatively, we could have a specific procedure that aborts the current
transaction and calls the continuation of the with-transaction call.
This could be implemented using the mechanism I just described without
exposing that it's actually an exception.  That would be even less ugly
at the cost of adding another procedure to the API.

> Are there problems with committing the transaction by default, but rolling
> back if an unhandled exception is raised inside the "with-transaction" bloc?

The ugliness I mentioned just now.

> Nested transactions happen when some of the code is in a library
> (or a separate module of the application) so that it doesn't know whether or
> not it's inside a transaction, and uses "with-transaction" to make sure it
> is.
>
> For this purpose, transactions also do not need to be nested on the database
> side. It's enough for Scheme to keep a "reference count" of how many
> transactions deep we are, and just create a DB engine transaction at the
> outermost level and commit/rollback at the outermost level.
>
> There are probably some exotic situations where you'd want to use those
> Postgres savepoints at the DB engine side to rollback only part of a
> transaction. I don't know anything about that; I'll let you decide.

Well, I think you just described that yourself: if a library wants to
abort its own transaction, it should not roll back the entire user
transaction.  That's the idea behind nested transactions: it's an
abstraction.  It's somewhat leaky due to the isolation levels not being
changeable, but other than that I think it's best to make use of
savepoints when available.

The real question is: what should we do with nested with-transaction
calls for engines that don't support them?  We could raise an exception,
but that would make such library code unusable.  Alternatively, we could
just ignore the nesting but that would change the semantics when the
library tries to do a rollback; it would either do nothing (which is
undesirable) or it would abort the user transaction completely (which is
probably not what was intended either).

Note that this is not a specific Postgres feature.  MySQL has savepoints
as well: https://dev.mysql.com/doc/refman/5.7/en/savepoint.html
And so does SQLite: https://sqlite.org/lang_savepoint.html

Perhaps we should treat databases that don't support this as second-class
and raise an exception when you try to roll back a nested transaction?
That way, the "happy path" will always work.  Not sure this is great
design, though.  Raising an exception when trying to make a nested
transaction could also work, but that means some library code is
completely unusable in certain databases (how many databases don't have
these, anyway?).

> > Let's please avoid macros unless absolutely necessary!  Macros don't
> > compose, but procedures do.
>
> I'm neutral on this. If there's a macro, it may be good to include an
> equivalent procedure just in case.

Agreed, if we must have a macro I'd like to have a procedure as well.

Cheers,
Peter