File Coverage

blib/lib/DBIx/TransactionManager.pm
Criterion Covered Total %
statement 12 68 17.6
branch 0 26 0.0
condition 0 7 0.0
subroutine 4 18 22.2
pod 2 7 28.5
total 18 126 14.2


line stmt bran cond sub pod time code
1             package DBIx::TransactionManager;
2 1     1   31838 use strict;
  1         2  
  1         37  
3 1     1   6 use warnings;
  1         2  
  1         26  
4 1     1   5 use Carp ();
  1         6  
  1         652  
5             our $VERSION = '1.13';
6              
7             sub new {
8 0     0 1   my ($class, $dbh) = @_;
9              
10 0 0         unless ($dbh) {
11 0           Carp::croak('missing mandatory parameter dbh');
12             }
13              
14             bless {
15 0           dbh => $dbh,
16             active_transactions => [],
17             rollbacked_in_nested_transaction => 0,
18             }, $class;
19             }
20              
21             sub txn_scope {
22 0     0 1   DBIx::TransactionManager::ScopeGuard->new( @_ );
23             }
24              
25             sub txn_begin {
26 0     0 0   my ($self, %args) = @_;
27              
28 0   0       my $caller = $args{caller} || [ caller(0) ];
29 0           my $txns = $self->{active_transactions};
30 0           my $rc = 1;
31 0 0 0       if (@$txns == 0 && $self->{dbh}->FETCH('AutoCommit')) {
32 0           $rc = $self->{dbh}->begin_work;
33             }
34 0 0         if ($rc) {
35 0           push @$txns, { caller => $caller, pid => $$ };
36             }
37             }
38              
39             sub txn_rollback {
40 0     0 0   my $self = shift;
41 0           my $txns = $self->{active_transactions};
42 0 0         return unless @$txns;
43              
44 0           my $current = pop @$txns;
45 0 0         if ( @$txns > 0 ) {
46             # already popped, so if there's anything, we're in a nested
47             # transaction, mate.
48 0           $self->{rollbacked_in_nested_transaction}++;
49             } else {
50 0           $self->{dbh}->rollback;
51 0           $self->_txn_end;
52             }
53             }
54              
55             sub txn_commit {
56 0     0 0   my $self = shift;
57 0           my $txns = $self->{active_transactions};
58 0 0         return unless @$txns;
59              
60 0 0         if ( $self->{rollbacked_in_nested_transaction} ) {
61 0           Carp::croak "tried to commit but already rollbacked in nested transaction.";
62             }
63              
64 0           my $current = pop @$txns;
65 0 0         if (@$txns == 0) {
66 0           $self->{dbh}->commit;
67 0           $self->_txn_end;
68             }
69             }
70              
71             sub _txn_end {
72 0     0     @{$_[0]->{active_transactions}} = ();
  0            
73 0           $_[0]->{rollbacked_in_nested_transaction} = 0;
74             }
75              
76 0     0 0   sub active_transactions { $_[0]->{active_transactions} }
77              
78             sub in_transaction {
79 0     0 0   my $self = shift;
80 0           my $txns = $self->{active_transactions};
81 0 0         return @$txns ? $txns->[0] : ();
82             }
83              
84             package DBIx::TransactionManager::ScopeGuard;
85 1     1   2125 use Try::Tiny;
  1         2453  
  1         467  
86              
87             sub new {
88 0     0     my($class, $obj, %args) = @_;
89              
90 0   0       my $caller = $args{caller} || [ caller(1) ];
91 0           $obj->txn_begin( caller => $caller );
92 0           bless [ 0, $obj, $caller, $$ ], $class;
93             }
94              
95             sub rollback {
96 0 0   0     return if $_[0]->[0]; # do not run twice.
97 0           $_[0]->[1]->txn_rollback;
98 0           $_[0]->[0] = 1;
99             }
100              
101             sub commit {
102 0 0   0     return if $_[0]->[0]; # do not run twice.
103 0           $_[0]->[1]->txn_commit;
104 0           $_[0]->[0] = 1;
105             }
106              
107             sub DESTROY {
108 0     0     my($dismiss, $obj, $caller, $pid) = @{ $_[0] };
  0            
109 0 0         return if $dismiss;
110              
111 0 0         if ( $$ != $pid ) {
112 0           return;
113             }
114              
115 0           warn( "Transaction was aborted without calling an explicit commit or rollback. (Guard created at $caller->[1] line $caller->[2])" );
116              
117             try {
118 0     0     $obj->txn_rollback;
119             } catch {
120 0     0     die "Rollback failed: $_";
121 0           };
122             }
123              
124             1;
125             __END__