File Coverage

blib/lib/DBIx/Transaction/db.pm
Criterion Covered Total %
statement 15 138 10.8
branch 0 38 0.0
condition 0 18 0.0
subroutine 5 22 22.7
pod 13 15 86.6
total 33 231 14.2


line stmt bran cond sub pod time code
1             #!perl;
2              
3             package DBIx::Transaction::db;
4              
5 1     1   6 use DBI;
  1         2  
  1         47  
6 1     1   6 use base q(DBI::db);
  1         2  
  1         618  
7 1     1   8 use strict;
  1         2  
  1         1429  
8 1     1   10 use warnings (FATAL => 'all');
  1         3  
  1         79  
9 1     1   7 use Carp qw(confess croak);
  1         2  
  1         1837  
10              
11             return 1;
12              
13             sub connected {
14 0     0 0   my ( $self, $dsn, $user, $credential, $attrs ) = @_;
15              
16 0 0         if ( $self->{AutoCommit} ) {
17 0           $self->{private_DBIx_Transaction_AutoCommit} = 1;
18             }
19             else {
20 0           $self->{private_DBIx_Transaction_AutoCommit} = 0;
21             }
22              
23 0           $self->{private_DBIx_Transaction_Level} = 0;
24 0           $self->{private_DBIx_Transaction_Error} = 0;
25              
26 0   0       my $method = $attrs->{dbi_connect_method} || $DBI::connect_via;
27              
28 0           $self->transaction_trace($method);
29              
30 0           return $self;
31             }
32              
33             sub transaction_trace {
34 0     0 1   my($self, $method) = @_;
35 0           my @vals = map { "$_=$self->{$_}" } map { "private_DBIx_Transaction_$_" }
  0            
  0            
36             qw(AutoCommit Level Error);
37              
38 0           $self->trace_msg("DBIx::Transaction: $method: " . join(" ", @vals), 3);
39             }
40              
41             sub transaction_level {
42 0     0 1   my $self = shift;
43 0           return $self->{private_DBIx_Transaction_Level};
44             }
45              
46             sub inc_transaction_level {
47 0     0 1   my $self = shift;
48 0           $self->{private_DBIx_Transaction_Level}++;
49 0           return $self->{private_DBIx_Transaction_Level};
50             }
51              
52             sub dec_transaction_level {
53 0     0 1   my $self = shift;
54 0 0         confess "Asked to decrement transaction level below zero!"
55             unless($self->{private_DBIx_Transaction_Level});
56 0           $self->{private_DBIx_Transaction_Level}--;
57 0           return $self->{private_DBIx_Transaction_Level};
58             }
59              
60             sub clear_transaction_error {
61 0     0 1   my $self = shift;
62 0           $self->{private_DBIx_Transaction_Error} = 0;
63 0           $self->{private_DBIx_Transaction_Error_Caller} = undef;
64 0           return;
65             }
66              
67             sub inc_transaction_error {
68 0     0 1   my($self, @caller) = @_;
69 0           $self->{private_DBIx_Transaction_Error}++;
70 0 0         if(@caller) {
71 0   0       $self->{private_DBIx_Transaction_Error_Caller} ||= [];
72 0           push(@{$self->{private_DBIx_Transaction_Error_Caller}}, \@caller);
  0            
73             }
74 0           return;
75             }
76              
77             sub transaction_error {
78 0     0 1   my $self = shift;
79 0           return $self->{private_DBIx_Transaction_Error};
80             }
81              
82             sub transaction_error_callers {
83 0     0 0   my $self = shift;
84 0 0         if($self->{private_DBIx_Transaction_Error_Caller}) {
85 0           return @{$self->{private_DBIx_Transaction_Error_Caller}};
  0            
86             } else {
87 0           return;
88             }
89             }
90              
91             sub close_transaction {
92 0     0 1   my $self = shift;
93 0           my $method = shift;
94 0           my $code = DBI::db->can($method);
95              
96 0           $self->{private_DBIx_Transaction_Level} = 0;
97 0           $self->clear_transaction_error;
98 0           $self->transaction_trace($method);
99 0           my $rv = $code->($self, @_);
100 0           return $rv;
101             }
102              
103             sub begin_work {
104 0     0 1   my $self = shift;
105 0 0         if(!$self->transaction_level) {
106 0           $self->inc_transaction_level;
107 0 0         if($self->{private_DBIx_Transaction_AutoCommit}) {
108 0           $self->transaction_trace('begin_work');
109 0           return DBI::db::begin_work($self, @_);
110             } else {
111 0           return 1;
112             }
113             } else {
114 0           $self->inc_transaction_level;
115 0           $self->transaction_trace('fake_begin_work');
116 0           return 1;
117             }
118             }
119              
120             sub commit {
121 0     0 1   my $self = shift;
122 0 0         if(my $error = $self->transaction_error) {
123 0           my $err = "commit() called after a transaction error or rollback!";
124 0 0         if(my @callers = $self->transaction_error_callers) {
125 0           foreach my $i (@callers) {
126 0           $err .= "\nError or rollback at: $i->[1] line $i->[2]";
127 0 0         if($i->[3]) {
128 0           $err .= " (Error String: $i->[3])";
129             }
130             }
131             }
132              
133 0           $self->set_err(1, $err);
134 0           return;
135             }
136              
137 0 0         if(my $l = $self->dec_transaction_level) {
138 0           $self->transaction_trace('fake_commit');
139 0           return 1;
140             }
141 0           return $self->close_transaction('commit', @_);
142             }
143              
144             sub rollback {
145 0     0 1   my $self = shift;
146 0 0         if(my $l = $self->dec_transaction_level) {
147 0           $self->transaction_trace('fake_rollback');
148 0           $self->inc_transaction_error(caller);
149 0           return 1;
150             }
151 0           return $self->close_transaction('rollback', @_);
152             }
153              
154             sub do {
155 0     0 1   my $self = shift;
156 0           my $rv = eval { DBI::db::do($self, @_); };
  0            
157 0 0         if($@) {
158 0           $self->inc_transaction_error(caller, $self->errstr);
159 0           croak $@;
160             }
161 0 0         if(!$rv) {
162 0           $self->inc_transaction_error(caller, $self->errstr);
163             }
164 0           return $rv;
165             }
166              
167             sub _when {
168 0     0     my($dbh, $return_value, $return_exception, $tries) = @_;
169 0   0       my $rv = !!($tries && ($return_exception || !$return_value));
170 0           return $rv;
171             }
172              
173             sub transaction {
174 0     0 1   my($self, $run, $tries, $when) = @_;
175 0           my($rv, $re);
176              
177 0           my $tried = 0;
178 0   0       $tries ||= 1;
179              
180 0 0 0       if(($tries != 1 || $when) && $self->transaction_level) {
      0        
181 0           croak "Transaction retry flow may only be set on the outermost transaction";
182             }
183              
184              
185 0   0       $when ||= \&_when;
186              
187              
188 0           do {
189 0 0         $self->set_err(0, "Retrying transaction ($tries tries left)")
190             if $tried;
191              
192 0           eval { $rv = $self->_transaction($run) };
  0            
193 0           $re = $@;
194 0 0         $tries-- unless $tries <= 0;
195 0           $tried++;
196             } while($when->($self, $rv, $re, $tries));
197              
198 0 0         if($re) {
199 0           die $re;
200             } else {
201 0           return $rv;
202             }
203             }
204              
205             sub _transaction {
206 0     0     my($self, $run) = @_;
207 0           my $rv;
208              
209 0           $self->begin_work;
210              
211 0           eval { $rv = $run->() };
  0            
212              
213 0 0         if(my $re = $@) {
    0          
214 0           $self->rollback;
215 0           croak $re;
216             } elsif(!$rv) {
217 0           my $err = $self->err;
218 0           my $errstr = $self->errstr;
219 0           my $state = $self->state;
220 0           $self->rollback;
221 0           $self->set_err($err, $errstr, $state);
222             } else {
223 0           $self->commit;
224             }
225              
226 0           return $rv;
227             }
228              
229             =pod
230              
231             =head1 NAME
232              
233             DBIx::Transaction::db - Database handle that is aware of nested transactions
234              
235             =head1 SYNOPSIS
236              
237             See L
238              
239             =head1 DESCRIPTION
240              
241             When you connect to a database using DBIx::Transaction, your database handle
242             will be a DBIx::Transaction::db object. These objects keep track of your
243             transaction state, allowing for transactions to occur within transactions,
244             and only sending "C" or "C" instructions to the underlying
245             database when the outermost transaction has completed. See L
246             for a more complete explanation.
247              
248             =head1 METHODS
249              
250             =head2 Overridden Methods
251              
252             The following methods are overridden by DBIx::Transaction::db:
253              
254             =over
255              
256             =item begin_work
257              
258             Start a transaction.
259              
260             If there are no transactions currently running, C will check
261             if C is enabled. If it is enabled, a C instruction
262             is sent to the underlying database layer. If C is disabled, we
263             assume that the database has already started a transaction for us, and do
264             nothing. This means that B
265             transaction>, even if C is enabled!
266              
267             If there is a transaction started, C simply records that a nested
268             transaction has started.
269              
270             C returns the result of the database's C call if it
271             makes one; otherwise it always returns true.
272              
273             =item rollback
274              
275             Abort a transaction.
276              
277             If there are no sub-transactions currently running, C will issue the
278             C call to the underlying database layer.
279              
280             If there are sub-transactions currently running, C notes that the
281             nested transaction has been aborted.
282              
283             If there is no transaction running at all, C will raise a fatal
284             error.
285              
286             =item commit
287              
288             If there are sub-transactions currently running, C records that this
289             transaction has completed successfully and does nothing to the underlying
290             database layer.
291              
292             If there are no sub-transactions currently running, C checks if
293             there have been any transaction errors. If there has been a transaction
294             error, C raises an exception. Otherwise, a C call is
295             issued to the underlying database layer.
296              
297             If there is no transaction running at all, C will raise a fatal
298             error. This error will contain a full stack trace, and should also contain
299             the file names and line numbers where any rollbacks or query failures
300             happened.
301              
302             =item do
303              
304             Calls L on your underlying database handle. If an error
305             occurs, this is recorded and you will not be able to issue a C
306             for the current transaction.
307              
308             =back
309              
310             =head2 Extra Methods
311              
312             The following method is provided for convienence in setting up database
313             transactions:
314              
315             =over
316              
317             =item transaction($coderef[, $tries[, $when]])
318              
319             Execute the code contained inside C<$coderef> within a transaction.
320             C<$coderef> is expected to return a scalar value.
321             If C<$coderef> returns true, the transaction is committed. If
322             C<$coderef> returns false or raises a fatal error, the transaction
323             is rolled back. The return value is the same as what is returned by
324             C<$coderef>.
325              
326             This method is supplied to make it easier to create nested transactions
327             out of many small transactions. Example:
328              
329             sub get_max_id {
330             my $dbh = shift;
331             # this will roll back if it can't get MAX(num)
332             $dbh->transaction(sub {
333             if(my($id) = $dbh->selectrow_array("SELECT MAX(num) FROM foo")) {
334             return $id;
335             } else {
336             return;
337             }
338             });
339             }
340            
341             sub do_something {
342             my($dbh, $num) = @_;
343             $dbh->transaction(sub {
344             $dbh->do("UPDATE foo SET bar = bar + 1 WHERE num = $num");
345             });
346             }
347            
348             sub do_many_things {
349             my $dbh = shift;
350             # if any of these sub-transactions roll back, the whole thing will roll
351             # back. Try repeating the transaction up to 5 times.
352             $dbh->transaction(sub {
353             if(
354             do_something($dbh, 1) &&
355             do_something($dbh, 2) &&
356             (my $id = get_max_id($dbh))
357             ) {
358             return do_something($dbh, $id);
359             } else {
360             return;
361             }
362             }, 5);
363             }
364              
365             =over
366              
367             =item Re-trying transactions
368              
369             If C<$tries> is specified, the transaction will be tried up to
370             C<$tries> times before giving up. (Default: 1) Specify a negative
371             value to re-try forever.
372              
373             B only the outermost transaction may attempt retries. This
374             is because if there is one failure within a transaction, the entire
375             transaction fails -- so any retries in nested transactions would have
376             to fail, by virtue of the previous attempt failing. If you try to set
377             up retries from inside a nested transaction, this will die with the
378             error "Transaction retry flow may only be set on the outermost transaction".
379              
380             C<$when> is an optional code reference that can be used to decide
381             if a transaction should be retried or not. It will be passed the
382             following arguments:
383              
384             =over
385              
386             =item The database handle (C<$dbh>)
387              
388             =item The return value of the transaction
389              
390             =item The exception raised by the transaction, if any (C<$@>)
391              
392             =item How many tries are left
393              
394             =back
395              
396             If the code reference returns true, the transaction will be run again.
397             If it returns false, the C<$dbh->transaction()> will finish, either
398             returning a value, or raising an exception if one was caused by
399             the last execution of C<$coderef>.
400              
401             The default handler for C<$when> is simply:
402              
403             sub {
404             my($dbh, $return_value, $return_exception, $tries) = @_;
405             return $tries && ($return_exception || !$return_value);
406             }
407              
408             =back
409              
410             =back
411              
412             =head2 Other Methods
413              
414             The following methods are used by the overridden methods. In most cases
415             you won't have to use them yourself.
416              
417             =over
418              
419             =item transaction_level
420              
421             Returns an integer value representing how deeply nested our transactions
422             currently are. eg; if we are in a top-level transaction, this returns "1";
423             if we are 4 transactions deep, this returns "4", if we are not in a transaction
424             at all, this returns "0". In some extreme cases this may be used to bail out
425             of a nested transaction safely, as in:
426              
427             $dbh->rollback while $dbh->transaction_level;
428              
429             But I would suggest that you structure your code so that each transaction
430             and sub-transaction bails out safely instead, as that's a lot easier to
431             trace and debug with confidence.
432              
433             =item transaction_error
434              
435             Returns a true value if a sub-transaction has rolled back, false otherwise.
436             Again, you could use this to back out of a transaction safely, but I'd suggest
437             just writing your code to handle this case on each transaction level instead.
438              
439             =item transaction_trace
440              
441             For debugging; If DBI's trace level is 3 or over, emit the current values
442             of all of the internal variables DBIx::Transaction uses to track it's
443             transaction states.
444              
445             =item inc_transaction_level
446              
447             Indicate that we have started a sub transaction by increasing
448             C by one. This is used by the C override
449             and should not be called directly.
450              
451             =item dec_transaction_level
452              
453             Indicate that we have finished a sub transaction by decrementing
454             C by one. If this results in a negative number
455             (meaning more transactions have been commited/rolled back than started),
456             C throws a fatal error. This is used by the
457             C and C methods and should not be called directly.
458              
459             =item inc_transaction_error
460              
461             Indicate that a sub-transaction has failed and that the entire
462             transaction should not be allowed to be committed. This is done
463             automatically whenever a sub-transaction issues a C.
464             Optional parameters are the package, filename, and line where
465             the transaction error occured. If provided, they will be used in
466             error messages relating to the rollback.
467              
468             =item clear_transaction_error
469              
470             Clear the transaction error flag. This flag is set whenever a
471             sub-transaction issues a C, and cleared whenever the
472             outermost transaction issues a C.
473              
474             =item close_transaction($method)
475              
476             Close the outermost transaction by calling C<$method>
477             ("C" or "C") on the underlying database layer and
478             resetting the DBIx::Transaction state. This method is used by the
479             C and C methods and you shouldn't need to use it yourself,
480             unless you wanted to forcibly bail out of an entire transaction without
481             calling C repeatedly, but as stated above, that's a bad idea,
482             isn't it?
483              
484             =back
485              
486             =head1 SEE ALSO
487              
488             L, L
489              
490             =head1 AUTHOR
491              
492             Tyler "Crackerjack" MacDonald
493              
494             =head1 LICENSE
495              
496             Copyright 2005 Tyler MacDonald
497             This is free software; you may redistribute it under the same terms as perl itself.
498              
499             =cut