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__ |