File Coverage

blib/lib/DBIx/Handler.pm
Criterion Covered Total %
statement 18 164 10.9
branch 0 80 0.0
condition 0 34 0.0
subroutine 6 33 18.1
pod 18 19 94.7
total 42 330 12.7


line stmt bran cond sub pod time code
1             package DBIx::Handler;
2 6     6   55031 use strict;
  6         33  
  6         140  
3 6     6   23 use warnings;
  6         8  
  6         209  
4             our $VERSION = '0.15';
5              
6 6     6   7766 use DBI 1.605;
  6         96371  
  6         310  
7 6     6   2467 use DBIx::TransactionManager 1.09;
  6         17575  
  6         130  
8 6     6   33 use Carp ();
  6         8  
  6         259  
9              
10             our $TxnTraceLevel = 0;
11              
12       0     sub _noop {}
13              
14             {
15 6     6   28 no warnings qw/once/;
  6         10  
  6         9982  
16             *connect = \&new;
17             }
18              
19             sub new {
20 0     0 1   my $class = shift;
21              
22 0 0         my $opts = scalar(@_) == 5 ? pop @_ : +{};
23             bless {
24             _connect_info => [@_],
25             _pid => undef,
26             _dbh => undef,
27             trace_query => $opts->{trace_query} || 0,
28             trace_ignore_if => $opts->{trace_ignore_if} || \&_noop,
29             result_class => $opts->{result_class} || undef,
30             on_connect_do => $opts->{on_connect_do} || undef,
31             on_disconnect_do => $opts->{on_disconnect_do} || undef,
32             no_ping => $opts->{no_ping} || 0,
33             dbi_class => $opts->{dbi_class} || 'DBI',
34 0   0       prepare_method => $opts->{prepare_method} || 'prepare',
      0        
      0        
      0        
      0        
      0        
      0        
      0        
35             }, $class;
36             }
37              
38             sub _connect {
39 0     0     my $self = shift;
40              
41 0           my $dbh = $self->{_dbh} = $self->{dbi_class}->connect(@{$self->{_connect_info}});
  0            
42 0 0         my $attr = @{$self->{_connect_info}} > 3 ? $self->{_connect_info}->[3] : {};
  0            
43              
44 0 0 0       if (DBI->VERSION > 1.613 && !exists $attr->{AutoInactiveDestroy}) {
45 0           $dbh->STORE(AutoInactiveDestroy => 1);
46             }
47              
48 0 0 0       if (!exists $attr->{RaiseError} && !exists $attr->{HandleError}) {
49 0           $dbh->STORE(RaiseError => 1);
50             }
51              
52 0 0 0       if ($dbh->FETCH('RaiseError') && !exists $attr->{PrintError}) {
53 0           $dbh->STORE(PrintError => 0);
54             }
55              
56 0           $self->{_pid} = $$;
57              
58 0           $self->_run_on('on_connect_do', $dbh);
59              
60 0           $dbh;
61             }
62              
63             sub dbh {
64 0     0 1   my $self = shift;
65 0 0         $self->_seems_connected or $self->_connect;
66             }
67              
68             sub _ping {
69 0     0     my ($self, $dbh) = @_;
70 0 0         $self->{no_ping} || $dbh->ping;
71             }
72              
73             sub _seems_connected {
74 0     0     my $self = shift;
75              
76 0 0         my $dbh = $self->{_dbh} or return;
77              
78 0 0         if ( $self->{_pid} != $$ ) {
79 0           $dbh->STORE(InactiveDestroy => 1);
80 0           $self->_in_txn_check;
81 0           delete $self->{txn_manager};
82 0           return;
83             }
84              
85 0 0 0       unless ($dbh->FETCH('Active') && $self->_ping($dbh)) {
86 0           $self->_in_txn_check;
87 0           $self->_disconnect;
88 0           return;
89             }
90              
91 0           $dbh;
92             }
93              
94             sub disconnect {
95 0     0 1   my $self = shift;
96              
97 0 0         $self->_seems_connected or return;
98 0           $self->_disconnect;
99             }
100              
101             sub _disconnect {
102 0     0     my $self = shift;
103 0 0         my $dbh = delete $self->{_dbh} or return;
104 0           delete $self->{txn_manager};
105 0           $self->_run_on('on_disconnect_do', $dbh);
106 0           $dbh->STORE(CachedKids => {});
107 0           $dbh->disconnect;
108             }
109              
110             sub _run_on {
111 0     0     my ($self, $mode, $dbh) = @_;
112 0 0         if ( my $on_connect_do = $self->{$mode} ) {
113 0 0         if (not ref($on_connect_do)) {
    0          
    0          
114 0           $dbh->do($on_connect_do);
115             } elsif (ref($on_connect_do) eq 'CODE') {
116 0           $on_connect_do->($dbh);
117             } elsif (ref($on_connect_do) eq 'ARRAY') {
118 0           $dbh->do($_) for @$on_connect_do;
119             } else {
120 0           Carp::croak("Invalid $mode: ".ref($on_connect_do));
121             }
122             }
123             }
124              
125 0     0     sub DESTROY { $_[0]->disconnect }
126              
127             sub result_class {
128 0     0 1   my ($self, $result_class) = @_;
129 0 0         $self->{result_class} = $result_class if $result_class;
130 0           $self->{result_class};
131             }
132              
133             sub trace_query {
134 0     0 1   my ($self, $flag) = @_;
135 0 0         $self->{trace_query} = $flag if defined $flag;
136 0           $self->{trace_query};
137             }
138              
139             sub trace_ignore_if {
140 0     0 1   my ($self, $callback) = @_;
141 0 0         $self->{trace_ignore_if} = $callback if defined $callback;
142 0           $self->{trace_ignore_if};
143             }
144              
145             sub no_ping {
146 0     0 1   my ($self, $enable) = @_;
147 0 0         $self->{no_ping} = $enable if defined $enable;
148 0           $self->{no_ping};
149             }
150              
151             sub prepare_method {
152 0     0 1   my ($self, $prepare_method) = @_;
153 0 0         $self->{prepare_method} = $prepare_method if $prepare_method;
154 0           $self->{prepare_method};
155             }
156              
157             sub query {
158 0     0 1   my ($self, $sql, @args) = @_;
159              
160 0           my $bind;
161 0 0         if (ref($args[0]) eq 'HASH') {
162 0           ($sql, $bind) = $self->replace_named_placeholder($sql, $args[0]);
163             }
164             else {
165 0 0         $bind = ref($args[0]) eq 'ARRAY' ? $args[0] : \@args;
166             }
167              
168 0           $sql = $self->trace_query_set_comment($sql);
169              
170 0           my $sth;
171 0           eval {
172 0           my $prepare_method = $self->{prepare_method};
173 0           $sth = $self->dbh->$prepare_method($sql);
174 0 0         $sth->execute(@{$bind || []});
  0            
175             };
176 0 0         if (my $error = $@) {
177 0           Carp::croak($error);
178             }
179              
180 0           my $result_class = $self->result_class;
181 0 0         $result_class ? $result_class->new($self, $sth) : $sth;
182             }
183              
184             sub replace_named_placeholder {
185 0     0 0   my ($self, $sql, $args) = @_;
186              
187 0           my %named_bind = %{$args};
  0            
188 0           my @bind;
189 0           $sql =~ s{:(\w+)}{
190 0 0         Carp::croak("$1 does not exists in hash") if !exists $named_bind{$1};
191 0 0 0       if ( ref $named_bind{$1} && ref $named_bind{$1} eq "ARRAY" ) {
192 0           push @bind, @{ $named_bind{$1} };
  0            
193 0           my $tmp = join ',', map { '?' } @{ $named_bind{$1} };
  0            
  0            
194 0           "($tmp)";
195             } else {
196 0           push @bind, $named_bind{$1};
197 0           '?'
198             }
199             }ge;
200              
201 0           return ($sql, \@bind);
202             }
203              
204             sub trace_query_set_comment {
205 0     0 1   my ($self, $sql) = @_;
206 0 0         return $sql unless $self->trace_query;
207              
208 0           my $i = 1;
209 0           while ( my (@caller) = caller($i++) ) {
210 0 0         next if ( $caller[0]->isa( __PACKAGE__ ) );
211 0 0         next if $self->trace_ignore_if->(@caller);
212 0           my $comment = "$caller[1] at line $caller[2]";
213 0           $comment =~ s/\*\// /g;
214 0           $sql = "/* $comment */ $sql";
215 0           last;
216             }
217              
218 0           $sql;
219             }
220              
221             sub run {
222 0     0 1   my ($self, $coderef) = @_;
223 0           my $wantarray = wantarray;
224              
225 0           my @ret = eval {
226 0           my $dbh = $self->dbh;
227 0 0         $wantarray ? $coderef->($dbh) : scalar $coderef->($dbh);
228             };
229 0 0         if (my $error = $@) {
230 0           Carp::croak($error);
231             }
232              
233 0 0         $wantarray ? @ret : $ret[0];
234             }
235              
236             # --------------------------------------------------------------------------------
237             # for transaction
238             sub txn_manager {
239 0     0 1   my $self = shift;
240              
241 0           my $dbh = $self->dbh;
242 0   0       $self->{txn_manager} ||= DBIx::TransactionManager->new($dbh);
243             }
244              
245             sub in_txn {
246 0     0 1   my $self = shift;
247 0 0         return unless $self->{txn_manager};
248 0           return $self->{txn_manager}->in_transaction;
249             }
250              
251             sub _in_txn_check {
252 0     0     my $self = shift;
253              
254 0           my $info = $self->in_txn;
255 0 0         return unless $info;
256              
257 0           my $caller = $info->{caller};
258 0           my $pid = $info->{pid};
259 0           Carp::confess("Detected transaction during a connect operation (last known transaction at $caller->[1] line $caller->[2], pid $pid). Refusing to proceed at");
260             }
261              
262             sub txn_scope {
263 0     0 1   my @caller = caller($TxnTraceLevel);
264 0           shift->txn_manager->txn_scope(caller => \@caller, @_);
265             }
266              
267             sub txn {
268 0     0 1   my ($self, $coderef) = @_;
269              
270 0           my $wantarray = wantarray;
271 0           my $txn = $self->txn_scope(caller => [caller($TxnTraceLevel)]);
272              
273 0           my @ret = eval {
274 0           my $dbh = $self->dbh;
275 0 0         $wantarray ? $coderef->($dbh) : scalar $coderef->($dbh);
276             };
277              
278 0 0         if (my $error = $@) {
279 0           $txn->rollback;
280 0           Carp::croak($error);
281             } else {
282 0           eval { $txn->commit };
  0            
283 0 0         Carp::croak($@) if $@;
284             }
285              
286 0 0         $wantarray ? @ret : $ret[0];
287             }
288              
289 0     0 1   sub txn_begin { $_[0]->txn_manager->txn_begin }
290 0     0 1   sub txn_rollback { $_[0]->txn_manager->txn_rollback }
291 0     0 1   sub txn_commit { $_[0]->txn_manager->txn_commit }
292              
293             1;
294              
295             __END__