I've been writing some Perl DBI code which involves some fairly involved error handling; I've been looking for a way to roll back transactions neatly when certain errors happen.
I very nearly reinvented the concept of a 'transaction scope guard' which I now find is implemented in DBIx::Class (with Scope::Guard implementing a more general version). A lexical variable can be used to detect in which cases a transaction should be ended, because the object it points to will get DESTROYed when it goes out of scope. Some rough code to illustrate the concept is below.
# $fallback controls whether we use nested transactions,
# which is slower but lets us commit all the other lines in the batch.
sub process_batch {
my $fallback = shift;
# This catches any exception handling that makes us leave the function.
# The DESTROY method of $transaction contains $db->rollback()
my $transaction = $db->txn_scope_guard();
for (1..2000) {
# File access error will get thrown to outside of function,
# so the transaction will be rolled back.
my $line = get_next_line();
last unless $line;
my $parsed;
eval {$parsed = parse_line($line)};
if ($@) {
# Handle line parsing errors here without interfering with
# DB error handling.
warn "Could not parse $line\n";
next;
}
# here's the actual db code
eval {
$db->savepoint() if $fallback;
$db->process_one_line($parsed);
}
if ($@) {
if ($fallback) {
# Just roll back to savepoint. Transaction continues.
$db->rollback_to_savepoint();
} else {
# Propagate error outside of function, ending transaction.
die $@;
}
}
}
$transaction->commit();
}
The advantage is that rollback code can be kept in one place, and not repeated all over the various error handling cases.
I had actually gone off this idea, because I couldn't see any documentation defining exactly when the DESTROY method gets called in Perl. But given it's got into DBIx::Class, it must be fine! I also prefer their API over what I was considering implementing; the transaction object that gets returned will handle only commit(), not any other database calls.