pg: Add transaction & subtransaction support

Was expecting the implementation of this to get overly complicated and
brittle, but using a counter-based cookie and doing parts of it in Perl
made it pretty easy actually.  Pretty happy with how this turned out so
far.

TODO: documentation -.-
This commit is contained in:
Yorhel 2025-02-06 17:38:33 +01:00
parent 9d5905e3b4
commit 171afc0268
5 changed files with 276 additions and 10 deletions

View file

@ -6,8 +6,58 @@ _load_libpq();
package FU::PG::conn {
sub lib_version { FU::PG::lib_version() }
sub txn($c) {
$c->exec('BEGIN');
$c->_set_cookie(++$FU::PG::txn::COUNTER);
bless [$c, $FU::PG::txn::COUNTER, undef], 'FU::PG::txn';
}
};
package FU::PG::txn {
use Carp 'croak';
my $COUNTER = 0;
# Arrayref:
# 0: $conn
# 1: $cookie, a snapshot of $COUNTER that identifies this transaction, used
# to match commands against transactions. Set to undef when this
# transaction is 'done' but the object is still alive.
# 2: $parent, undef if this is a top-level transaction.
sub commit($t) {
croak "Unable to commit transaction that has already finished" if !$t->[1];
$t->exec($t->[2] ? "RELEASE SAVEPOINT fupg_$t->[1]" : 'COMMIT');
$t->[1] = undef;
}
sub rollback($t) {
croak "Unable to rollback transaction that has already finished" if !$t->[1];
$t->exec($t->[2] ? "ROLLBACK TO SAVEPOINT fupg_$t->[1]" : 'ROLLBACK');
$t->[1] = undef;
}
sub txn($t) {
croak "Unable to create sub-transaction when current transaction has already finished" if !$t->[1];
$COUNTER++;
$t->exec("SAVEPOINT fupg_$COUNTER");
$t->[0]->_set_cookie($COUNTER);
bless [$t->[0], $COUNTER, $t], 'FU::PG::txn';
}
sub status($t) {
my $cs = $t->[0]->status;
return $cs if $cs eq 'bad' || ($t->[1] && $t->[0]->_get_cookie != $t->[1]);
return $cs eq 'txn_error' ? 'error' : $t->[1] ? 'idle' : 'done';
}
sub DESTROY($t) {
$t->rollback if $t->[1];
$t->[0]->_set_cookie($t->[2] ? $t->[2][1] : 0);
}
}
package FU::PG::error {
use overload '""' => sub($e, @) { $e->{full_message} };
}