File Coverage

lib/DBD/Safe.pm
Criterion Covered Total %
statement 182 204 89.2
branch 49 62 79.0
condition 27 34 79.4
subroutine 37 41 90.2
pod 0 1 0.0
total 295 342 86.2


line stmt bran cond sub pod time code
1             package DBD::Safe;
2             BEGIN {
3 2     2   6934 $DBD::Safe::VERSION = '0.06';
4             }
5              
6 2     2   18 use strict;
  2         4  
  2         72  
7 2     2   12 use warnings;
  2         4  
  2         86  
8              
9             #ABSTRACT: keep safe connection to DB
10              
11              
12 2     2   10 use base qw(DBD::File);
  2         4  
  2         5040  
13              
14 2     2   158454 use vars qw($err $errstr $sqlstate $drh);
  2         4  
  2         814  
15              
16             sub DESTROY {
17 0     0   0 shift->STORE(Active => 0);
18             }
19              
20             $err = 0; # DBI::err
21             $errstr = ""; # DBI::errstr
22             $sqlstate = ""; # DBI::state
23             $drh = undef;
24              
25             sub driver {
26 2     2 0 2084 my ($class, $attr) = @_;
27 2 50       8 return $drh if $drh;
28              
29 2         8 DBI->setup_driver($class);
30              
31             # x_ allowed only from 1.54
32 2 50       62 if ($DBI::VERSION > 1.53) {
33 2         24 DBD::Safe::db->install_method('x_safe_get_dbh');
34             }
35              
36 2         126 my $self = $class->SUPER::driver({
37             Name => 'Safe',
38             Version => $DBD::Safe::VERSION,
39             Err => \$DBD::Safe::err,
40             Errstr => \$DBD::Safe::errstr,
41             State => \$DBD::Safe::sqlstate,
42             Attribution => 'DBD::Safe',
43             });
44 2         352 return $self;
45             }
46              
47             sub CLONE {
48 0     0   0 undef $drh;
49             }
50              
51             #######################################################################
52             package DBD::Safe::dr;
53             BEGIN {
54 2     2   36 $DBD::Safe::dr::VERSION = '0.06';
55             }
56              
57 2     2   12 use strict;
  2         4  
  2         196  
58 2     2   10 use warnings;
  2         4  
  2         134  
59              
60             $DBD::Safe::dr::imp_data_size = 0;
61 2     2   10 use DBD::File;
  2         4  
  2         48  
62 2     2   10 use DBI qw();
  2         2  
  2         42  
63 2     2   8 use base qw(DBD::File::dr);
  2         4  
  2         1616  
64              
65             sub connect {
66 22     22   8534 my($drh, $dbname, $user, $auth, $attr) = @_;
67              
68 22         32 my $connect_cb;
69 22 50       84 if ($attr->{connect_cb}) {
    50          
70 0         0 $connect_cb = $attr->{connect_cb};
71             } elsif ($attr->{dbi_connect_args}) {
72 22     28   74 $connect_cb = sub { DBI->connect(@{$attr->{dbi_connect_args}}) };
  28         36  
  28         243  
73             } else {
74 0         0 die "No connect way defined\n";
75             #return $drh->set_err($DBI::stderr, "No connect way defined");
76             }
77              
78             my $retry_cb = sub {
79 28     28   31 my $try = shift;
80 28 50       61 if ($try == 1) {
81 28         62 return 1;
82             } else {
83 0         0 return 0;
84             }
85 22         148 };
86 22 100       72 $retry_cb = $attr->{retry_cb} if ($attr->{retry_cb});
87              
88 22     31   58 my $reconnect_cb = sub { 0 };
  31         592  
89 22 100       107 $reconnect_cb = $attr->{reconnect_cb} if ($attr->{reconnect_cb});
90              
91              
92 22         49 my $reconnect_period = $attr->{reconnect_period};
93              
94 22         136 my $dbh = DBI::_new_dbh(
95             $drh => {
96             Name => 'safedb',
97             USER => $user,
98             CURRENT_USER => $user,
99             },
100             );
101 22         948 $dbh->STORE(Active => 1);
102              
103 22         75 $dbh->STORE('x_safe_connect_cb' => $connect_cb);
104 22         76 $dbh->STORE('x_safe_state' => {});
105 22         72 $dbh->STORE('x_safe_retry_cb' => $retry_cb);
106 22         68 $dbh->STORE('x_safe_reconnect_cb' => $reconnect_cb);
107              
108 22         86 return $dbh;
109             }
110              
111             #######################################################################
112             package DBD::Safe::db;
113             BEGIN {
114 2     2   28 $DBD::Safe::db::VERSION = '0.06';
115             }
116              
117 2     2   10 use strict;
  2         6  
  2         48  
118 2     2   18 use warnings;
  2         4  
  2         50  
119              
120 2     2   10 use Time::HiRes qw(time);
  2         2  
  2         18  
121              
122             $DBD::Safe::db::imp_data_size = 0;
123              
124             my $LOCAL_ATTRIBUTES = {
125             PrintError => 1,
126             RaiseError => 1,
127             Active => 1,
128             AutoCommit => 1,
129             };
130              
131 2     2   324 use vars qw($AUTOLOAD);
  2         4  
  2         1160  
132              
133             sub prepare;
134             sub column_info;
135             sub last_insert_id;
136              
137             sub begin_work {
138 5     5   919 my $dbh = shift;
139              
140 5 100       29 if (!$dbh->FETCH('AutoCommit')) {
141 1         9 die "Already in a transaction\n";
142             }
143 4         20 $dbh->STORE('AutoCommit', 0);
144              
145 4         16 my $in_transaction = $dbh->FETCH('x_safe_in_transaction');
146 4         9 $in_transaction++;
147 4         12 $dbh->STORE('x_safe_in_transaction', $in_transaction);
148 4         19 $dbh->STORE('x_safe_transaction_start', time());
149              
150 4         13 return _proxy_method('begin_work', $dbh, @_);
151             }
152              
153             sub _do_commit_or_rollback {
154 10     10   30 my ($dbh, $f, @args) = @_;
155              
156 10 100       56 if ($dbh->FETCH('AutoCommit')) {
157 2         20 die "$f() without begin_work()\n";
158             }
159              
160 8         53 my $in_transaction = $dbh->FETCH('x_safe_in_transaction');
161 8 100       28 return _proxy_method($f, $dbh, @args) unless ($in_transaction);
162              
163 3         5 $in_transaction--;
164 3         5 my $error = 0;
165 3 50       9 if ($in_transaction < 0) {
166 0         0 $in_transaction = 0;
167 0         0 $error = 1;
168             }
169 3         12 $dbh->STORE('x_safe_in_transaction', $in_transaction);
170              
171 3 50       9 if ($error) {
172 0         0 die "$f() without begin_work()\n";
173             #$dbh->set_err(0, "commit() without begin_work()");
174             }
175              
176 3 100       8 if ($f eq 'rollback') {
177 1   50     7 my $tr_start = $dbh->FETCH('x_safe_transaction_start') || 0;
178 1   50     16 my $last_reconnect = $dbh->FETCH('x_safe_state')->{last_reconnect} || 0;
179 1 50       5 if ($last_reconnect > $tr_start) {
180 0         0 die "Disconnect occured during transaction, can't call rollback()\n";
181             }
182             }
183              
184 3         9 my $res = _proxy_method($f, $dbh, @args);
185 2 50       36 if ($in_transaction == 0) {
186 2         9 $dbh->STORE('AutoCommit', 1);
187             }
188 2         8 return $res;
189             }
190              
191             sub commit {
192 5     5   2312 my $dbh = shift;
193              
194 5         20 return _do_commit_or_rollback($dbh, 'commit', @_);
195             }
196              
197             sub rollback {
198 5     5   1523 my $dbh = shift;
199              
200 5         18 return _do_commit_or_rollback($dbh, 'rollback', @_);
201             }
202              
203             sub _proxy_method {
204 12     12   24 my ($method, $dbh, @args) = @_;
205 12         38 my $state = $dbh->FETCH('x_safe_state');
206 12         23 my $real_dbh = stay_connected($dbh);
207 8         122 return $real_dbh->$method(@args);
208             }
209              
210             # TODO: take a more accurate logic from DBD::Proxy
211             sub AUTOLOAD {
212 0     0   0 my $method = $AUTOLOAD;
213 0         0 $method =~ s/(.*::(.*)):://;
214 0         0 my $class = $1;
215 0         0 my $type = $2;
216              
217             my $s = sub {
218 0     0   0 return _proxy_method($method, @_)
219 0         0 };
220              
221 2     2   12 no strict 'refs';
  2         2  
  2         1802  
222 0         0 *{$AUTOLOAD} = $s;
  0         0  
223 0         0 goto &$s;
224             }
225              
226             sub x_safe_get_dbh {
227             # $dont_check is a special flag for FETCH and STORE methods
228 94     94   2577341 my ($dbh, $dont_check) = @_;
229              
230             # doesn't call here FETCH avoiding recursion
231 94         188 my $state = $dbh->{x_safe_state};
232 94 100 33     664 if (!$state || !$state->{dbh} || !$dont_check) {
      66        
233 50         148 stay_connected($dbh);
234 45         83 $state = $dbh->{x_safe_state};
235             }
236              
237 89         297 return $state->{dbh};
238             }
239              
240             sub disconnect {
241 22     22   37 my ($dbh) = @_;
242              
243 22         102 $dbh->STORE(Active => 0);
244              
245 22         478 1;
246             }
247              
248             sub _attr_is_local {
249 292     292   514 my $attr = shift;
250 292 50       574 return 0 unless defined($attr);
251 292 100       972 return 1 if ($attr =~ /^(x_safe_|private_)/);
252 185 100       574 return 1 if ($LOCAL_ATTRIBUTES->{$attr});
253 69         170 return 0;
254             }
255              
256             sub STORE {
257 284     284   10410 my ($dbh, $attr, $val) = @_;
258              
259 284 100       627 if (_attr_is_local($attr)) {
260 215         790 $dbh->{$attr} = $val;
261              
262             # because of some old DBI bug
263 215 100       1172 if ($attr eq 'Active') {
264 44         646 my $v = $dbh->FETCH($attr);
265             }
266              
267             # if ($LOCAL_ATTRIBUTES->{$attr}) {
268             # my $caller = caller(1);
269             # my $real_dbh = stay_connected($dbh);
270             # $real_dbh->{$attr} => $val if ($real_dbh);
271             # }
272             } else {
273 69         128 my $real_dbh = x_safe_get_dbh($dbh, 'dont_check');
274 65         444 $real_dbh->STORE($attr => $val);
275             }
276             }
277              
278             sub FETCH {
279 8     8   14 my ($dbh, $attr) = @_;
280              
281 8 50       38 if (_attr_is_local($attr)) {
282 8         23 return $dbh->{$attr};
283             } else {
284 0         0 my $real_dbh = x_safe_get_dbh($dbh, 'dont_check');
285 0         0 return $real_dbh->FETCH($attr);
286             }
287             }
288              
289             sub DESTROY {
290 22     22   19181 my $dbh = shift;
291 22         156 $dbh->disconnect;
292             }
293              
294             sub stay_connected {
295 62     62   84 my $dbh = shift;
296 62         285 my ($caller, $f) = (caller(1))[0,3];
297              
298 62         2131 my $state = $dbh->FETCH('x_safe_state');
299 62         179 my $reconnect_cb = $dbh->FETCH('x_safe_reconnect_cb');
300              
301 62         87 my $reconnect = 0;
302 62 100       210 if ($state->{dbh}) {
303 37 100 100     117 if (
      66        
      100        
      100        
304             $reconnect_cb->($dbh) ||
305             (defined($state->{tid}) && $state->{tid} != threads->tid) ||
306             ($state->{pid} != $$) ||
307             (!is_connected($dbh))
308             )
309             {
310 12         88 $reconnect = 1;
311              
312 12 100       68 if ($state->{pid} != $$) {
313 1         238 $state->{dbh}->{InactiveDestroy} = 1;
314             }
315             }
316             } else {
317 25         31 $reconnect = 1;
318             }
319              
320 62 100       927 if ($reconnect) {
321 37         174 $state->{last_reconnect} = time();
322 37 100 100     189 if ($state->{dbh} && !$dbh->FETCH('AutoCommit')) {
323 5         37 die "Reconnect needed when db in transaction\n";
324             #return $dbh->set_err($DBI::stderr, "Reconnect needed when db in transaction");
325             }
326              
327 32         37 my $try = 0;
328 32         97 my $retry_cb = $dbh->FETCH('x_safe_retry_cb');
329 32         53 while (1) {
330 32         36 $try++;
331 32         70 my $can_connect = $retry_cb->($try);
332 32 100       78 if ($can_connect) {
333 28         82 my $dbh = eval { real_connect($dbh) };
  28         198  
334 28 50       70 if (!$dbh) {
335 0         0 next;
336             } else {
337 28         68 $state->{dbh} = $dbh;
338 28         70 last;
339             }
340             } else {
341 4   50     24 my $error = $state->{last_error} || '';
342 4         8 chomp($error);
343              
344 4         42 die "All tries to connect is ended, can't connect: [$error]\n";
345             #return $dbh->set_err(
346             # $DBI::stderr,
347             # "All tries to connect is ended, can't connect: [$error]"
348             #);
349             }
350             }
351             }
352              
353 53         200 return $state->{dbh};
354             }
355              
356             sub is_connected {
357 31     31   80 my $dbh = shift;
358              
359 31         90 my $state = $dbh->FETCH('x_safe_state');
360              
361 31   100     426 my $active = $state->{dbh}->{Active} || '';
362 31   100     711 my $ping = $state->{dbh}->ping || '';
363              
364 31   100     509 return $active && $ping;
365             }
366              
367             sub real_connect {
368 28     28   34 my $dbh = shift;
369              
370 28         87 my $connect_cb = $dbh->FETCH('x_safe_connect_cb');
371 28         83 my $state = $dbh->FETCH('x_safe_state');
372              
373 28         33 my $real_dbh;
374 28         32 eval {
375 28         69 $real_dbh = $connect_cb->();
376             # for (keys %{$LOCAL_ATTRIBUTES}) {
377             # $real_dbh->{$_} = $dbh->FETCH($_);
378             # }
379             };
380 28 50       30100 if ($@) {
381 0         0 $state->{last_error} = $@;
382             } else {
383 28         113 $state->{last_connected} = time();
384             }
385              
386 28         83 $state->{pid} = $$;
387 28 100       121 $state->{tid} = threads->tid if $INC{'threads.pm'};
388              
389 28         75 return $real_dbh;
390             }
391              
392             1;
393              
394             package DBD::Safe::st;
395             BEGIN {
396 2     2   84 $DBD::Safe::st::VERSION = '0.06';
397             }
398              
399             $DBD::Safe::st::imp_data_size = 0;
400              
401             1;
402              
403             __END__