File Coverage

blib/lib/DBI/Gofer/Execute.pm
Criterion Covered Total %
statement 248 295 84.0
branch 99 156 63.4
condition 58 115 50.4
subroutine 23 25 92.0
pod 0 11 0.0
total 428 602 71.1


line stmt bran cond sub pod time code
1             package DBI::Gofer::Execute;
2              
3             # $Id: Execute.pm 14282 2010-07-26 00:12:54Z David $
4             #
5             # Copyright (c) 2007, Tim Bunce, Ireland
6             #
7             # You may distribute under the terms of either the GNU General Public
8             # License or the Artistic License, as specified in the Perl README file.
9              
10 52     52   231 use strict;
  52         86  
  52         1862  
11 52     52   335 use warnings;
  52         201  
  52         1391  
12              
13 52     52   194 use Carp;
  52         72  
  52         3001  
14              
15 52     52   241 use DBI qw(dbi_time);
  52         73  
  52         2183  
16 52     52   408 use DBI::Gofer::Request;
  52         89  
  52         1430  
17 52     52   266 use DBI::Gofer::Response;
  52         90  
  52         2263  
18              
19 52     52   224 use base qw(DBI::Util::_accessor);
  52         83  
  52         167414  
20              
21             our $VERSION = "0.014283";
22              
23             our @all_dbh_methods = sort map { keys %$_ } $DBI::DBI_methods{db}, $DBI::DBI_methods{common};
24             our %all_dbh_methods = map { $_ => (DBD::_::db->can($_)||undef) } @all_dbh_methods;
25              
26             our $local_log = $ENV{DBI_GOFER_LOCAL_LOG}; # do extra logging to stderr
27              
28             our $current_dbh; # the dbh we're using for this request
29              
30              
31             # set trace for server-side gofer
32             # Could use DBI_TRACE env var when it's an unrelated separate process
33             # but using DBI_GOFER_TRACE makes testing easier for subprocesses (eg stream)
34             DBI->trace(split /=/, $ENV{DBI_GOFER_TRACE}, 2) if $ENV{DBI_GOFER_TRACE};
35              
36              
37             # define valid configuration attributes (args to new())
38             # the values here indicate the basic type of values allowed
39             my %configuration_attributes = (
40             gofer_execute_class => 1,
41             default_connect_dsn => 1,
42             forced_connect_dsn => 1,
43             default_connect_attributes => {},
44             forced_connect_attributes => {},
45             track_recent => 1,
46             check_request_sub => sub {},
47             check_response_sub => sub {},
48             forced_single_resultset => 1,
49             max_cached_dbh_per_drh => 1,
50             max_cached_sth_per_dbh => 1,
51             forced_response_attributes => {},
52             forced_gofer_random => 1,
53             stats => {},
54             );
55              
56             __PACKAGE__->mk_accessors(
57             keys %configuration_attributes
58             );
59              
60              
61              
62             sub new {
63 52     52 0 106 my ($self, $args) = @_;
64 52   50     365 $args->{default_connect_attributes} ||= {};
65 52   50     309 $args->{forced_connect_attributes} ||= {};
66 52   50     246 $args->{max_cached_sth_per_dbh} ||= 1000;
67 52   50     246 $args->{stats} ||= {};
68 52         355 return $self->SUPER::new($args);
69             }
70              
71              
72             sub valid_configuration_attributes {
73 0     0 0 0 my $self = shift;
74 0         0 return { %configuration_attributes };
75             }
76              
77              
78             my %extra_attr = (
79             # Only referenced if the driver doesn't support private_attribute_info method.
80             # What driver-specific attributes should be returned for the driver being used?
81             # keyed by $dbh->{Driver}{Name}
82             # XXX for sth should split into attr specific to resultsets (where NUM_OF_FIELDS > 0) and others
83             # which would reduce processing/traffic for non-select statements
84             mysql => {
85             dbh => [qw(
86             mysql_errno mysql_error mysql_hostinfo mysql_info mysql_insertid
87             mysql_protoinfo mysql_serverinfo mysql_stat mysql_thread_id
88             )],
89             sth => [qw(
90             mysql_is_blob mysql_is_key mysql_is_num mysql_is_pri_key mysql_is_auto_increment
91             mysql_length mysql_max_length mysql_table mysql_type mysql_type_name mysql_insertid
92             )],
93             # XXX this dbh_after_sth stuff is a temporary, but important, hack.
94             # should be done via hash instead of arrays where the hash value contains
95             # flags that can indicate which attributes need to be handled in this way
96             dbh_after_sth => [qw(
97             mysql_insertid
98             )],
99             },
100             Pg => {
101             dbh => [qw(
102             pg_protocol pg_lib_version pg_server_version
103             pg_db pg_host pg_port pg_default_port
104             pg_options pg_pid
105             )],
106             sth => [qw(
107             pg_size pg_type pg_oid_status pg_cmd_status
108             )],
109             },
110             Sybase => {
111             dbh => [qw(
112             syb_dynamic_supported syb_oc_version syb_server_version syb_server_version_string
113             )],
114             sth => [qw(
115             syb_types syb_proc_status syb_result_type
116             )],
117             },
118             SQLite => {
119             dbh => [qw(
120             sqlite_version
121             )],
122             sth => [qw(
123             )],
124             },
125             ExampleP => {
126             dbh => [qw(
127             examplep_private_dbh_attrib
128             )],
129             sth => [qw(
130             examplep_private_sth_attrib
131             )],
132             dbh_after_sth => [qw(
133             examplep_insertid
134             )],
135             },
136             );
137              
138              
139             sub _connect {
140 4904     4904   5128 my ($self, $request) = @_;
141              
142 4904         5576 my $stats = $self->{stats};
143              
144             # discard CachedKids from time to time
145 4904 50 66     14751 if (++$stats->{_requests_served} % 1000 == 0 # XXX config?
146             and my $max_cached_dbh_per_drh = $self->{max_cached_dbh_per_drh}
147             ) {
148 0         0 my %drivers = DBI->installed_drivers();
149 0         0 while ( my ($driver, $drh) = each %drivers ) {
150 0 0       0 next unless my $CK = $drh->{CachedKids};
151 0 0       0 next unless keys %$CK > $max_cached_dbh_per_drh;
152 0 0       0 next if $driver eq 'Gofer'; # ie transport=null when testing
153 0         0 DBI->trace_msg(sprintf "Clearing %d cached dbh from $driver",
154             scalar keys %$CK, $self->{max_cached_dbh_per_drh});
155 0   0     0 $_->{Active} && $_->disconnect for values %$CK;
156 0         0 %$CK = ();
157             }
158             }
159              
160             # local $ENV{...} can leak, so only do it if required
161 4904 100       18053 local $ENV{DBI_AUTOPROXY} if $ENV{DBI_AUTOPROXY};
162              
163 4904         4683 my ($connect_method, $dsn, $username, $password, $attr) = @{ $request->dbh_connect_call };
  4904         10540  
164 4904   50     8800 $connect_method ||= 'connect_cached';
165 4904         7954 $stats->{method_calls_dbh}->{$connect_method}++;
166              
167             # delete attributes we don't want to affect the server-side
168             # (Could just do this on client-side and trust the client. DoS?)
169 4904         5495 delete @{$attr}{qw(Profile InactiveDestroy AutoInactiveDestroy HandleError HandleSetErr TraceLevel Taint TaintIn TaintOut)};
  4904         11709  
170              
171 4904 50 33     10707 $dsn = $self->forced_connect_dsn || $dsn || $self->default_connect_dsn
172             or die "No forced_connect_dsn, requested dsn, or default_connect_dsn for request";
173              
174 4904   100     24708 my $random = $self->{forced_gofer_random} || $ENV{DBI_GOFER_RANDOM} || '';
175              
176 4904         10906 my $connect_attr = {
177              
178             # the configured default attributes, if any
179 4904         10818 %{ $self->default_connect_attributes },
180              
181             # pass username and password as attributes
182             # then they can be overridden by forced_connect_attributes
183             Username => $username,
184             Password => $password,
185              
186             # the requested attributes
187             %$attr,
188              
189             # force some attributes the way we'd like them
190             PrintWarn => $local_log,
191             PrintError => $local_log,
192              
193             # the configured default attributes, if any
194 4904         5366 %{ $self->forced_connect_attributes },
195              
196             # RaiseError must be enabled
197             RaiseError => 1,
198              
199             # reset Executed flag (of the cached handle) so we can use it to tell
200             # if errors happened before the main part of the request was executed
201             Executed => 0,
202              
203             # ensure this connect_cached doesn't have the same args as the client
204             # because that causes subtle issues if in the same process (ie transport=null)
205             # include pid to avoid problems with forking (ie null transport in mod_perl)
206             # include gofer-random to avoid random behaviour leaking to other handles
207             dbi_go_execute_unique => join("|", __PACKAGE__, $$, $random),
208             };
209              
210             # XXX implement our own private connect_cached method? (with rate-limited ping)
211 4904         19876 my $dbh = DBI->$connect_method($dsn, undef, undef, $connect_attr);
212              
213 4878 50       9565 $dbh->{ShowErrorStatement} = 1 if $local_log;
214              
215             # XXX should probably just be a Callbacks => arg to connect_cached
216             # with a cache of pre-built callback hooks (memoized, without $self)
217 4878 100 66     27410 if (my $random = $self->{forced_gofer_random} || $ENV{DBI_GOFER_RANDOM}) {
218 2900         5517 $self->_install_rand_callbacks($dbh, $random);
219             }
220              
221 4878         19512 my $CK = $dbh->{CachedKids};
222 4878 50 33     18530 if ($CK && keys %$CK > $self->{max_cached_sth_per_dbh}) {
223 0         0 %$CK = (); # clear all statement handles
224             }
225              
226             #$dbh->trace(0);
227 4878         5559 $current_dbh = $dbh;
228 4878         19928 return $dbh;
229             }
230              
231              
232             sub reset_dbh {
233 4878     4878 0 5890 my ($self, $dbh) = @_;
234 4878         26082 $dbh->set_err(undef, undef); # clear any error state
235             }
236              
237              
238             sub new_response_with_err {
239 4904     4904 0 8024 my ($self, $rv, $eval_error, $dbh) = @_;
240             # this is the usual way to create a response for both success and failure
241             # capture err+errstr etc and merge in $eval_error ($@)
242              
243 4904         32744 my ($err, $errstr, $state) = ($DBI::err, $DBI::errstr, $DBI::state);
244              
245 4904 100       10412 if ($eval_error) {
246 1599   50     2906 $err ||= $DBI::stderr || 1; # ensure err is true
      66        
247 1599 100       2458 if ($errstr) {
248 1597 50       19982 $eval_error =~ s/(?: : \s)? \Q$errstr//x if $errstr;
249 1597         2913 chomp $errstr;
250 1597         2931 $errstr .= "; $eval_error";
251             }
252             else {
253 2         4 $errstr = $eval_error;
254             }
255             }
256 4904 100       8841 chomp $errstr if $errstr;
257              
258 4904         4391 my $flags;
259             # (XXX if we ever add transaction support then we'll need to take extra
260             # steps because the commit/rollback would reset Executed before we get here)
261 4904 100 100     34639 $flags |= GOf_RESPONSE_EXECUTED if $dbh && $dbh->{Executed};
262              
263 4904         43954 my $response = DBI::Gofer::Response->new({
264             rv => $rv,
265             err => $err,
266             errstr => $errstr,
267             state => $state,
268             flags => $flags,
269             });
270              
271 4904         15868 return $response;
272             }
273              
274              
275             sub execute_request {
276 4904     4904 0 5678 my ($self, $request) = @_;
277             # should never throw an exception
278              
279 4904         12558 DBI->trace_msg("-----> execute_request\n");
280              
281 4904         4852 my @warnings;
282             local $SIG{__WARN__} = sub {
283 22     22   195 push @warnings, @_;
284 22 50       76 warn @_ if $local_log;
285 4904         29949 };
286              
287 4904         5834 my $response = eval {
288              
289 4904 50       11824 if (my $check_request_sub = $self->check_request_sub) {
290 0 0       0 $request = $check_request_sub->($request, $self)
291             or die "check_request_sub failed";
292             }
293              
294 4904   50     11658 my $version = $request->version || 0;
295 4904 50 33     29523 die ref($request)." version $version is not supported"
296             if $version < 0.009116 or $version >= 1;
297              
298 4904 100       11815 ($request->is_sth_request)
299             ? $self->execute_sth_request($request)
300             : $self->execute_dbh_request($request);
301             };
302 4904   33     19190 $response ||= $self->new_response_with_err(undef, $@, $current_dbh);
303              
304 4904 50       12454 if (my $check_response_sub = $self->check_response_sub) {
305             # not protected with an eval so it can choose to throw an exception
306 0         0 my $new = $check_response_sub->($response, $self, $request);
307 0 0       0 $response = $new if ref $new;
308             }
309              
310 4904         6325 undef $current_dbh;
311              
312 4904 100       27618 $response->warnings(\@warnings) if @warnings;
313 4904         37670 DBI->trace_msg("<----- execute_request\n");
314 4904         33797 return $response;
315             }
316              
317              
318             sub execute_dbh_request {
319 3657     3657 0 4272 my ($self, $request) = @_;
320 3657         4440 my $stats = $self->{stats};
321              
322 3657         3249 my $dbh;
323 3657   100     3227 my $rv_ref = eval {
324             $dbh = $self->_connect($request);
325             my $args = $request->dbh_method_call; # [ wantarray, 'method_name', @args ]
326             my $wantarray = shift @$args;
327             my $meth = shift @$args;
328             $stats->{method_calls_dbh}->{$meth}++;
329             my @rv = ($wantarray)
330             ? $dbh->$meth(@$args)
331             : scalar $dbh->$meth(@$args);
332             \@rv;
333             } || [];
334 3657         11768 my $response = $self->new_response_with_err($rv_ref, $@, $dbh);
335              
336 3657 100       7170 return $response if not $dbh;
337              
338             # does this request also want any dbh attributes returned?
339 3631 100       8470 if (my $dbh_attributes = $request->dbh_attributes) {
340 687         2209 $response->dbh_attributes( $self->gather_dbh_attributes($dbh, $dbh_attributes) );
341             }
342              
343 3631 50 33     11757 if ($rv_ref and my $lid_args = $request->dbh_last_insert_id_args) {
344 0         0 $stats->{method_calls_dbh}->{last_insert_id}++;
345 0         0 my $id = $dbh->last_insert_id( @$lid_args );
346 0         0 $response->last_insert_id( $id );
347             }
348              
349 3631 100 66     19140 if ($rv_ref and UNIVERSAL::isa($rv_ref->[0],'DBI::st')) {
350             # dbh_method_call was probably a metadata method like table_info
351             # that returns a statement handle, so turn the $sth into resultset
352 10         30 my $sth = $rv_ref->[0];
353 10         45 $response->sth_resultsets( $self->gather_sth_resultsets($sth, $request, $response) );
354 10         39 $response->rv("(sth)"); # don't try to return actual sth
355             }
356              
357             # we're finished with this dbh for this request
358 3631         7130 $self->reset_dbh($dbh);
359              
360 3631         13361 return $response;
361             }
362              
363              
364             sub gather_dbh_attributes {
365 1878     1878 0 2812 my ($self, $dbh, $dbh_attributes) = @_;
366 1878         4262 my @req_attr_names = @$dbh_attributes;
367 1878 50       5223 if ($req_attr_names[0] eq '*') { # auto include std + private
368 1878         2114 shift @req_attr_names;
369 1878         2356 push @req_attr_names, @{ $self->_std_response_attribute_names($dbh) };
  1878         3833  
370             }
371 1878         2306 my %dbh_attr_values;
372 1878         8109 @dbh_attr_values{@req_attr_names} = $dbh->FETCH_many(@req_attr_names);
373              
374             # XXX piggyback installed_methods onto dbh_attributes for now
375 1878         16541 $dbh_attr_values{dbi_installed_methods} = { DBI->installed_methods };
376              
377             # XXX piggyback default_methods onto dbh_attributes for now
378 1878         5662 $dbh_attr_values{dbi_default_methods} = _get_default_methods($dbh);
379              
380 1878         6381 return \%dbh_attr_values;
381             }
382              
383              
384             sub _std_response_attribute_names {
385 3079     3079   3705 my ($self, $h) = @_;
386 3079   33     6852 $h = tied(%$h) || $h; # switch to inner handle
387              
388             # cache the private_attribute_info data for each handle
389             # XXX might be better to cache it in the executor
390             # as it's unlikely to change
391             # or perhaps at least cache it in the dbh even for sth
392             # as the sth are typically very short lived
393              
394 3079         3285 my ($dbh, $h_type, $driver_name, @attr_names);
395              
396 3079 100       6816 if ($dbh = $h->{Database}) { # is an sth
397              
398             # does the dbh already have the answer cached?
399 1201 100       2902 return $dbh->{private_gofer_std_attr_names_sth} if $dbh->{private_gofer_std_attr_names_sth};
400              
401 1139         3498 ($h_type, $driver_name) = ('sth', $dbh->{Driver}{Name});
402 1139         4167 push @attr_names, qw(NUM_OF_PARAMS NUM_OF_FIELDS NAME TYPE NULLABLE PRECISION SCALE);
403             }
404             else { # is a dbh
405 1878 100       4313 return $h->{private_gofer_std_attr_names_dbh} if $h->{private_gofer_std_attr_names_dbh};
406              
407 1774         5415 ($h_type, $driver_name, $dbh) = ('dbh', $h->{Driver}{Name}, $h);
408             # explicitly add these because drivers may have different defaults
409             # add Name so the client gets the real Name of the connection
410 1774         5472 push @attr_names, qw(ChopBlanks LongReadLen LongTruncOk ReadOnly Name);
411             }
412              
413 2913 100       14248 if (my $pai = $h->private_attribute_info) {
414 775         3722 push @attr_names, keys %$pai;
415             }
416             else {
417 2138 100       10761 push @attr_names, @{ $extra_attr{ $driver_name }{$h_type} || []};
  2138         10056  
418             }
419 2913 50       7196 if (my $fra = $self->{forced_response_attributes}) {
420 0 0       0 push @attr_names, @{ $fra->{ $driver_name }{$h_type} || []}
  0         0  
421             }
422 2913         19161 $dbh->trace_msg("_std_response_attribute_names for $driver_name $h_type: @attr_names\n");
423              
424             # cache into the dbh even for sth, as the dbh is usually longer lived
425 2913         19592 return $dbh->{"private_gofer_std_attr_names_$h_type"} = \@attr_names;
426             }
427              
428              
429             sub execute_sth_request {
430 1247     1247 0 1871 my ($self, $request) = @_;
431 1247         1446 my $dbh;
432             my $sth;
433 0         0 my $last_insert_id;
434 1247         2072 my $stats = $self->{stats};
435              
436 1247         1570 my $rv = eval {
437 1247         2848 $dbh = $self->_connect($request);
438              
439 1247         4207 my $args = $request->dbh_method_call; # [ wantarray, 'method_name', @args ]
440 1247         1992 shift @$args; # discard wantarray
441 1247         2287 my $meth = shift @$args;
442 1247         2865 $stats->{method_calls_sth}->{$meth}++;
443 1247         6169 $sth = $dbh->$meth(@$args);
444 1191         7414 my $last = '(sth)'; # a true value (don't try to return actual sth)
445              
446             # execute methods on the sth, e.g., bind_param & execute
447 1191 100       3709 if (my $calls = $request->sth_method_calls) {
448 769         1741 for my $meth_call (@$calls) {
449 4489         6329 my $method = shift @$meth_call;
450 4489         5677 $stats->{method_calls_sth}->{$method}++;
451 4489         12392 $last = $sth->$method(@$meth_call);
452             }
453             }
454              
455 1167 50       7281 if (my $lid_args = $request->dbh_last_insert_id_args) {
456 0         0 $stats->{method_calls_sth}->{last_insert_id}++;
457 0         0 $last_insert_id = $dbh->last_insert_id( @$lid_args );
458             }
459              
460 1167         2754 $last;
461             };
462 1247         5616 my $response = $self->new_response_with_err($rv, $@, $dbh);
463              
464 1247 50       3280 return $response if not $dbh;
465              
466 1247 50       2948 $response->last_insert_id( $last_insert_id )
467             if defined $last_insert_id;
468              
469             # even if the eval failed we still want to try to gather attribute values
470             # (XXX would be nice to be able to support streaming of results.
471             # which would reduce memory usage and latency for large results)
472 1247 100       2735 if ($sth) {
473 1191         3569 $response->sth_resultsets( $self->gather_sth_resultsets($sth, $request, $response) );
474 1191         4190 $sth->finish;
475             }
476              
477             # does this request also want any dbh attributes returned?
478 1247         4158 my $dbh_attr_set;
479 1247 100       3343 if (my $dbh_attributes = $request->dbh_attributes) {
480 1191         3259 $dbh_attr_set = $self->gather_dbh_attributes($dbh, $dbh_attributes);
481             }
482             # XXX needs to be integrated with private_attribute_info() etc
483 1247 100       7572 if (my $dbh_attr = $extra_attr{$dbh->{Driver}{Name}}{dbh_after_sth}) {
484 591         2917 @{$dbh_attr_set}{@$dbh_attr} = $dbh->FETCH_many(@$dbh_attr);
  591         1880  
485             }
486 1247 100 66     16621 $response->dbh_attributes($dbh_attr_set) if $dbh_attr_set && %$dbh_attr_set;
487              
488 1247         3279 $self->reset_dbh($dbh);
489              
490 1247         18603 return $response;
491             }
492              
493              
494             sub gather_sth_resultsets {
495 1201     1201 0 1807 my ($self, $sth, $request, $response) = @_;
496 1201         1792 my $resultsets = eval {
497              
498 1201         3301 my $attr_names = $self->_std_response_attribute_names($sth);
499 1201         1805 my $sth_attr = {};
500 1201         8774 $sth_attr->{$_} = 1 for @$attr_names;
501              
502             # let the client add/remove sth attributes
503 1201 100       3926 if (my $sth_result_attr = $request->sth_result_attr) {
504             $sth_attr->{$_} = $sth_result_attr->{$_}
505 1191         3293 for keys %$sth_result_attr;
506             }
507 1201         3674 my @sth_attr = grep { $sth_attr->{$_} } keys %$sth_attr;
  8982         10574  
508              
509 1201         1832 my $row_count = 0;
510 1201         1801 my $rs_list = [];
511 1201         1292 while (1) {
512 1201         3316 my $rs = $self->fetch_result_set($sth, \@sth_attr);
513 1201         2225 push @$rs_list, $rs;
514 1201 100       3123 if (my $rows = $rs->{rowset}) {
515 578         983 $row_count += @$rows;
516             }
517 1201 50       2690 last if $self->{forced_single_resultset};
518 1201 50 33     5563 last if !($sth->more_results || $sth->{syb_more_results});
519             }
520              
521 1201         6468 my $stats = $self->{stats};
522 1201         2075 $stats->{rows_returned_total} += $row_count;
523 1201 100 100     4720 $stats->{rows_returned_max} = $row_count
524             if $row_count > ($stats->{rows_returned_max}||0);
525              
526 1201         4573 $rs_list;
527             };
528 1201 50       2468 $response->add_err(1, $@) if $@;
529 1201         4353 return $resultsets;
530             }
531              
532              
533             sub fetch_result_set {
534 1201     1201 0 1657 my ($self, $sth, $sth_attr) = @_;
535 1201         1262 my %meta;
536 1201         1475 eval {
537 1201         6023 @meta{ @$sth_attr } = $sth->FETCH_many(@$sth_attr);
538             # we assume @$sth_attr contains NUM_OF_FIELDS
539 1201 100 100     13779 $meta{rowset} = $sth->fetchall_arrayref()
540             if (($meta{NUM_OF_FIELDS}||0) > 0); # is SELECT
541             # the fetchall_arrayref may fail with a 'not executed' kind of error
542             # because gather_sth_resultsets/fetch_result_set are called even if
543             # execute() failed, or even if there was no execute() call at all.
544             # The corresponding error goes into the resultset err, not the top-level
545             # response err, so in most cases this resultset err is never noticed.
546             };
547 1201 100       7001 if ($@) {
548 224         493 chomp $@;
549 224   100     1156 $meta{err} = $DBI::err || 1;
550 224   66     1235 $meta{errstr} = $DBI::errstr || $@;
551 224         537 $meta{state} = $DBI::state;
552             }
553 1201         2597 return \%meta;
554             }
555              
556              
557             sub _get_default_methods {
558 1878     1878   2397 my ($dbh) = @_;
559             # returns a ref to a hash of dbh method names for methods which the driver
560             # hasn't overridden i.e., quote(). These don't need to be forwarded via gofer.
561 1878 50       10668 my $ImplementorClass = $dbh->{ImplementorClass} or die;
562 1878         8890 my %default_methods;
563 1878         4331 for my $method (@all_dbh_methods) {
564 108924   100     198116 my $dbi_sub = $all_dbh_methods{$method} || 42;
565 108924   100     327139 my $imp_sub = $ImplementorClass->can($method) || 42;
566 108924 100       167534 next if $imp_sub != $dbi_sub;
567             #warn("default $method\n");
568 80072         111806 $default_methods{$method} = 1;
569             }
570 1878         6131 return \%default_methods;
571             }
572              
573              
574             # XXX would be nice to make this a generic DBI module
575             sub _install_rand_callbacks {
576 2900     2900   3323 my ($self, $dbh, $dbi_gofer_random) = @_;
577              
578 2900   100     15644 my $callbacks = $dbh->{Callbacks} || {};
579 2900   100     12507 my $prev = $dbh->{private_gofer_rand_fail_callbacks} || {};
580              
581             # return if we've already setup this handle with callbacks for these specs
582 2900 100 100     11589 return if (($callbacks->{_dbi_gofer_random_spec}||'') eq $dbi_gofer_random);
583             #warn "$dbh # $callbacks->{_dbi_gofer_random_spec}";
584 7         15 $callbacks->{_dbi_gofer_random_spec} = $dbi_gofer_random;
585              
586 7         8 my ($fail_percent, $fail_err, $delay_percent, $delay_duration, %spec_part, @spec_note);
587 7         29 my @specs = split /,/, $dbi_gofer_random;
588 7         14 for my $spec (@specs) {
589 14 100       55 if ($spec =~ m/^fail=(-?[.\d]+)%?$/) {
590 6         13 $fail_percent = $1;
591 6         15 $spec_part{fail} = $spec;
592 6         11 next;
593             }
594 8 50       18 if ($spec =~ m/^err=(-?\d+)$/) {
595 0         0 $fail_err = $1;
596 0         0 $spec_part{err} = $spec;
597 0         0 next;
598             }
599 8 100       56 if ($spec =~ m/^delay([.\d]+)=(-?[.\d]+)%?$/) {
    50          
600 1         4 $delay_duration = $1;
601 1         2 $delay_percent = $2;
602 1         2 $spec_part{delay} = $spec;
603 1         2 next;
604             }
605             elsif ($spec !~ m/^(\w+|\*)$/) {
606 0         0 warn "Ignored DBI_GOFER_RANDOM item '$spec' which isn't a config or a dbh method name";
607 0         0 next;
608             }
609              
610 7         10 my $method = $spec;
611 7 0 33     26 if ($callbacks->{$method} && $prev->{$method} && $callbacks->{$method} != $prev->{$method}) {
      33        
612 0         0 warn "Callback for $method method already installed so DBI_GOFER_RANDOM callback not installed\n";
613 0         0 next;
614             }
615 7 50 66     27 unless (defined $fail_percent or defined $delay_percent) {
616 0         0 warn "Ignored DBI_GOFER_RANDOM item '$spec' because not preceded by 'fail=N' and/or 'delayN=N'";
617 0         0 next;
618             }
619              
620 7         23 push @spec_note, join(",", values(%spec_part), $method);
621 7         29 $callbacks->{$method} = $self->_mk_rand_callback($method, $fail_percent, $delay_percent, $delay_duration, $fail_err);
622             }
623 7 50       70 warn "DBI_GOFER_RANDOM failures/delays enabled: @spec_note\n"
624             if @spec_note;
625 7         40 $dbh->{Callbacks} = $callbacks;
626 7         34 $dbh->{private_gofer_rand_fail_callbacks} = $callbacks;
627             }
628              
629             my %_mk_rand_callback_seqn;
630              
631             sub _mk_rand_callback {
632 7     7   14 my ($self, $method, $fail_percent, $delay_percent, $delay_duration, $fail_err) = @_;
633 7         12 my ($fail_modrate, $delay_modrate);
634 7 100 100     16 $fail_percent ||= 0; $fail_modrate = int(1/(-$fail_percent )*100) if $fail_percent;
  7         32  
635 7 100 100     24 $delay_percent ||= 0; $delay_modrate = int(1/(-$delay_percent)*100) if $delay_percent;
  7         15  
636             # note that $method may be "*" but that's not recommended or documented or wise
637             return sub {
638 2900     2900   3203 my ($h) = @_;
639 2900         3831 my $seqn = ++$_mk_rand_callback_seqn{$method};
640 2900 50       5944 my $delay = ($delay_percent > 0) ? rand(100) < $delay_percent :
    100          
641             ($delay_percent < 0) ? !($seqn % $delay_modrate): 0;
642 2900 100       7421 my $fail = ($fail_percent > 0) ? rand(100) < $fail_percent :
    100          
643             ($fail_percent < 0) ? !($seqn % $fail_modrate) : 0;
644             #no warnings 'uninitialized';
645             #warn "_mk_rand_callback($fail_percent:$fail_modrate, $delay_percent:$delay_modrate): seqn=$seqn fail=$fail delay=$delay";
646 2900 100       5042 if ($delay) {
647 11         37 my $msg = "DBI_GOFER_RANDOM delaying execution of $method() by $delay_duration seconds\n";
648             # Note what's happening in a trace message. If the delay percent is an even
649             # number then use warn() instead so it's sent back to the client.
650 11 50       81 ($delay_percent % 2 == 1) ? warn($msg) : $h->trace_msg($msg);
651 11         1102497 select undef, undef, undef, $delay_duration; # allows floating point value
652             }
653 2900 100       4632 if ($fail) {
654 1487         1761 undef $_; # tell DBI to not call the method
655             # the "induced by DBI_GOFER_RANDOM" is special and must be included in errstr
656             # as it's checked for in a few places, such as the gofer retry logic
657 1487   33     32065 return $h->set_err($fail_err || $DBI::stderr,
658             "fake error from $method method induced by DBI_GOFER_RANDOM env var ($fail_percent%)");
659             }
660 1413         5410 return;
661             }
662 7         69 }
663              
664              
665             sub update_stats {
666 0     0 0   my ($self,
667             $request, $response,
668             $frozen_request, $frozen_response,
669             $time_received,
670             $store_meta, $other_meta,
671             ) = @_;
672              
673             # should always have a response object here
674 0 0         carp("No response object provided") unless $request;
675              
676 0           my $stats = $self->{stats};
677 0 0 0       $stats->{frozen_request_max_bytes} = length($frozen_request)
      0        
678             if $frozen_request
679             && length($frozen_request) > ($stats->{frozen_request_max_bytes}||0);
680 0 0 0       $stats->{frozen_response_max_bytes} = length($frozen_response)
      0        
681             if $frozen_response
682             && length($frozen_response) > ($stats->{frozen_response_max_bytes}||0);
683              
684 0           my $recent;
685 0 0         if (my $track_recent = $self->{track_recent}) {
686 0 0         $recent = {
687             request => $frozen_request,
688             response => $frozen_response,
689             time_received => $time_received,
690             duration => dbi_time()-$time_received,
691             # for any other info
692             ($store_meta) ? (meta => $store_meta) : (),
693             };
694 0 0 0       $recent->{request_object} = $request
695             if !$frozen_request && $request;
696 0 0         $recent->{response_object} = $response
697             if !$frozen_response;
698 0   0       my @queues = ($stats->{recent_requests} ||= []);
699 0 0 0       push @queues, ($stats->{recent_errors} ||= [])
      0        
700             if !$response or $response->err;
701 0           for my $queue (@queues) {
702 0           push @$queue, $recent;
703 0 0         shift @$queue if @$queue > $track_recent;
704             }
705             }
706 0           return $recent;
707             }
708              
709              
710             1;
711             __END__