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";

        # here's the actual db code
        eval {
            $db->savepoint() if $fallback;
        if ($@) {
            if ($fallback) {
                # Just roll back to savepoint.  Transaction continues.
            } else {
                # Propagate error outside of function, ending transaction.
                die $@;


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.