File Coverage

blib/lib/DBIx/Handler.pm
Criterion Covered Total %
statement 15 157 9.5
branch 0 78 0.0
condition 0 32 0.0
subroutine 5 31 16.1
pod 17 18 94.4
total 37 316 11.7


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