use strict;
use warnings;
package DB::Transaction;
use base qw(Exporter);

our $VERSION = 0.001000; # 0.001.0

our @EXPORT_OK = qw(run_in_transaction);

my %on_error_options = (
       rollback => undef,
       continue => undef,
);

my %transactions_deep;
sub run_in_transaction (&;@) {
       my $code = shift;
       my (%args) = @_;

       my $db_handle = $args{db_handle};
       my $on_error = $args{on_error} || 'rollback';

       die "Don't know how to handle error action '$on_error'\n"
               if ! exists $on_error_options{$on_error};

       local $db_handle->{AutoCommit} = 0;
       local $db_handle->{RaiseError} = 1;

       $transactions_deep{"$db_handle"}++;
       my $error;
       eval {
               $code->();
               $db_handle->commit if $transactions_deep{"$db_handle"} <= 1;
       ;1 } || do {
               $error = defined $@ ? $@ : 'an error was encountered in your transaction';
               $db_handle->rollback if $on_error eq 'rollback';
       };
       $@ = $error if defined $error;
       $transactions_deep{"$db_handle"}--;
       delete $transactions_deep{"$db_handle"} if $transactions_deep{"$db_handle"} < 1;

       die $error if defined $error && $on_error eq 'rollback';

       return ! defined $error;
}

1;

__END__

=head1 NAME

DB::Transaction - feather-weight transaction management for your DBI handles

=head1 SYNOPSIS

   use DB::Transaction qw(run_in_transaction);

   my $dbh = My::Application->get_dbh;

   run_in_transaction {
       $dbh->do('
           update risky_business -- in some fashion
       ');
   } db_handle => $dbh, on_error => 'rollback';

=head1 DESCRIPTION

DB::Transaction provides one function: run_in_transaction

=head1 EXPORTS

By default, none. On request, C<run_in_transaction>.

=head2 run_in_transaction BLOCK db_handle => $db_handle, on_error => ['rollback' | 'continue']

Begin a transaction on $db_handle, then run BLOCK. Any errors raised in the
course of executing BLOCK will cause the current transaction to be handled
according to your C<on_error> specification.

C<on_error> may be one of these two options:

=over 4

=item * rollback -- call this dbh's ->rollback method

=item * continue -- just keep on chugging, man!

=back

C<on_error =E<gt> 'rollback'> is the default behavior.

Transactions may be nested, though your underlying database may not support
nested transactions. It's up to you to know whether this is supported or not.

=head1 CONTRIBUTING

To contribute back to this project, log in to your GitHub account and visit
L<http://github.com/shutterstock/perl-db-transaction>, then fork the repository.

Create a feature branch, make your changes, push them back to your fork, and
submit a pull request via GitHub.

   # fork the project in github

   git clone git://github.com/<your-name>/perl-db-transaction.git
   git checkout -b feature-add-spiffy-functionality

   emacs -nw t/spiffy-functionality.t   # hack hack hack
   emacs -nw lib/DB/Transaction.pm      # hack hack hack

   git push feature-add-spiffy-functionality origin

   # submit pull request via github

=head1 AUTHORS

Written by Aaron Cohen <[email protected]> and Belden Lyman <[email protected]>
at Shutterstock, Inc. Released to CPAN by Shutterstock, Inc.

If you like the idea of working at a company that supports open-source development,
why not checkout our L<jobs page|http://shutterstock.com/jobs.mhtml> and drop us a
line?

=head1 COPYRIGHT AND LICENSE

   (c) 2013 Shutterstock, Inc. All rights reserved.

This library is free software: you may redistribute it and/or modify it under the same terms as Perl itself;
either Perl version 5.8.8 or, at your option, any later version of Perl 5 you may have available.