File Coverage

inc/DBD/Mock.pm
Criterion Covered Total %
statement 92 641 14.3
branch 17 316 5.3
condition 4 71 5.6
subroutine 26 93 27.9
pod 0 59 0.0
total 139 1180 11.7


line stmt bran cond sub pod time code
1             #line 1
2             package DBD::Mock;
3              
4 0     0   0 sub import {
5 0 0 0     0 shift;
6             $DBI::connect_via = "DBD::Mock::Pool::connect" if (@_ && lc($_[0]) eq "pool");
7             }
8              
9             # --------------------------------------------------------------------------- #
10             # Copyright (c) 2004-2007 Stevan Little, Chris Winters
11             # (spawned from original code Copyright (c) 1994 Tim Bunce)
12             # --------------------------------------------------------------------------- #
13             # You may distribute under the terms of either the GNU General Public
14             # License or the Artistic License, as specified in the Perl README file.
15             # --------------------------------------------------------------------------- #
16 1     1   1242  
  1         3  
  1         33  
17             use 5.008001;
18 1     1   5  
  1         3  
  1         37  
19 1     1   5 use strict;
  1         2  
  1         379  
20             use warnings;
21              
22             require DBI;
23              
24             our $VERSION = '1.39';
25              
26             our $drh = undef; # will hold driver handle
27             our $err = 0; # will hold any error codes
28             our $errstr = ''; # will hold any error messages
29              
30 1 50   1 0 180 sub driver {
31 1         5 return $drh if defined $drh;
32 1 50 33     13 my ($class, $attributes) = @_;
33 1         18 $attributes = {} unless (defined($attributes) && (ref($attributes) eq 'HASH'));
34             $drh = DBI::_new_drh( "${class}::dr", {
35             Name => 'Mock',
36             Version => $DBD::Mock::VERSION,
37             Attribution => 'DBD Mock driver by Chris Winters & Stevan Little (orig. from Tim Bunce)',
38             Err => \$DBD::Mock::err,
39             Errstr => \$DBD::Mock::errstr,
40             # mock attributes
41             mock_connect_fail => 0,
42 1         4 # and pass in any extra attributes given
43             %{$attributes}
44 1         66 });
45             return $drh;
46             }
47 0     0   0  
48             sub CLONE { undef $drh }
49              
50             # NOTE:
51             # this feature is still quite experimental. It is defaulted to
52             # be off, but it can be turned on by doing this:
53             # $DBD::Mock::AttributeAliasing++;
54             # and then turned off by doing:
55             # $DBD::Mock::AttributeAliasing = 0;
56             # we shall see how this feature works out.
57              
58             our $AttributeAliasing = 0;
59              
60             my %AttributeAliases = (
61             mysql => {
62             db => {
63             # aliases can either be a string which is obvious
64             mysql_insertid => 'mock_last_insert_id'
65             },
66             st => {
67             # but they can also be a subroutine reference whose
68             # first argument will be either the $dbh or the $sth
69             # depending upon which context it is aliased in.
70             mysql_insertid => sub { (shift)->{Database}->{'mock_last_insert_id'} }
71             }
72             },
73             );
74              
75 0     0   0 sub _get_mock_attribute_aliases {
76 0 0       0 my ($dbname) = @_;
77             (exists $AttributeAliases{lc($dbname)})
78 0         0 || die "Attribute aliases not available for '$dbname'";
79             return $AttributeAliases{lc($dbname)};
80             }
81              
82 0     0   0 sub _set_mock_attribute_aliases {
83 0         0 my ($dbname, $dbh_or_sth, $key, $value) = @_;
84             return $AttributeAliases{lc($dbname)}->{$dbh_or_sth}->{$key} = $value;
85             }
86              
87             ## Some useful constants
88 1     1   5  
  1         1  
  1         88  
89             use constant NULL_RESULTSET => [[]];
90              
91              
92             ########################################
93             # DRIVER
94              
95             package
96             DBD::Mock::dr;
97 1     1   5  
  1         2  
  1         27  
98 1     1   5 use strict;
  1         1  
  1         1285  
99             use warnings;
100              
101             $DBD::Mock::dr::imp_data_size = 0;
102              
103 1     1 0 56 sub connect {
104 1 50       7 my ($drh, $dbname, $user, $auth, $attributes) = @_;
105 0         0 if ($drh->{'mock_connect_fail'} == 1) {
106 0         0 $drh->DBI::set_err(1, "Could not connect to mock database");
107             return;
108 1   50     4 }
109             $attributes ||= {};
110 1 0 33     4  
111             if ($dbname && $DBD::Mock::AttributeAliasing) {
112 0         0 # this is the DB we are mocking
113 0         0 $attributes->{mock_attribute_aliases} = DBD::Mock::_get_mock_attribute_aliases($dbname);
114             $attributes->{mock_database_name} = $dbname;
115             }
116              
117 1         4 # holds statement parsing coderefs/objects
118             $attributes->{mock_parser} = [];
119 1         2 # holds all statements applied to handle until manually cleared
120             $attributes->{mock_statement_history} = [];
121 1         3 # ability to fake a failed DB connection
122             $attributes->{mock_can_connect} = 1;
123 1         2 # ability to make other things fail :)
124 1         3 $attributes->{mock_can_prepare} = 1;
125 1         2 $attributes->{mock_can_execute} = 1;
126             $attributes->{mock_can_fetch} = 1;
127 1   50     7  
128             my $dbh = DBI::_new_dbh($drh, {Name => $dbname})
129             || return;
130 1         52  
131             return $dbh;
132             }
133              
134 0     0   0 sub FETCH {
135 0 0       0 my ($drh, $attr) = @_;
136 0 0       0 if ($attr =~ /^mock_/) {
    0          
137 0         0 if ($attr eq 'mock_connect_fail') {
138             return $drh->{'mock_connect_fail'};
139             }
140 0 0       0 elsif ($attr eq 'mock_data_sources') {
141 0         0 unless (defined $drh->{'mock_data_sources'}) {
142             $drh->{'mock_data_sources'} = [ 'DBI:Mock:' ];
143 0         0 }
144             return $drh->{'mock_data_sources'};
145             }
146 0         0 else {
147             return $drh->SUPER::FETCH($attr);
148             }
149             }
150 0         0 else {
151             return $drh->SUPER::FETCH($attr);
152             }
153             }
154              
155 0     0   0 sub STORE {
156 0 0       0 my ($drh, $attr, $value) = @_;
157 0 0       0 if ($attr =~ /^mock_/) {
    0          
    0          
158 0 0       0 if ($attr eq 'mock_connect_fail') {
159             return $drh->{'mock_connect_fail'} = $value ? 1 : 0;
160             }
161 0 0       0 elsif ($attr eq 'mock_data_sources') {
162 0         0 if (ref($value) ne 'ARRAY') {
163 0         0 $drh->DBI::set_err(1, "You must pass an array ref of data sources");
164             return;
165 0         0 }
166             return $drh->{'mock_data_sources'} = $value;
167             }
168 0         0 elsif ($attr eq 'mock_add_data_sources') {
  0         0  
169             return push @{$drh->{'mock_data_sources'}} => $value;
170             }
171             }
172 0         0 else {
173             return $drh->SUPER::STORE($attr, $value);
174             }
175             }
176              
177 0     0 0 0 sub data_sources {
178 0 0       0 my $drh = shift;
  0         0  
  0         0  
179             return map { (/^DBI\:Mock\:/i) ? $_ : "DBI:Mock:$_" } @{$drh->FETCH('mock_data_sources')};
180             }
181              
182             # Necessary to support DBI < 1.34
183             # from CPAN RT bug #7057
184 1     1 0 44  
185             sub disconnect_all {
186             # no-op
187             }
188 0     0   0  
189             sub DESTROY { undef }
190              
191             ########################################
192             # DATABASE
193              
194             package
195             DBD::Mock::db;
196 1     1   14  
  1         2  
  1         31  
197 1     1   5 use strict;
  1         3  
  1         2635  
198             use warnings;
199              
200             $DBD::Mock::db::imp_data_size = 0;
201              
202 0     0 0 0 sub ping {
203 0         0 my ( $dbh ) = @_;
204             return $dbh->{mock_can_connect};
205             }
206              
207 0     0 0 0 sub last_insert_id {
208 0         0 my ( $dbh ) = @_;
209             return $dbh->{mock_last_insert_id};
210             }
211              
212 0     0 0 0 sub get_info {
213 0   0     0 my ( $dbh, $attr ) = @_;
214 0         0 $dbh->{mock_get_info} ||= {};
215             return $dbh->{mock_get_info}{ $attr };
216             }
217              
218 0     0 0 0 sub prepare {
219             my($dbh, $statement) = @_;
220 0 0       0  
221 0         0 unless ($dbh->{mock_can_connect}) {
222 0         0 $dbh->DBI::set_err(1, "No connection present");
223             return;
224 0 0       0 }
225 0         0 unless ($dbh->{mock_can_prepare}) {
226 0         0 $dbh->DBI::set_err(1, "Cannot prepare");
227             return;
228 0 0       0 }
229             $dbh->{mock_can_prepare}++ if $dbh->{mock_can_prepare} < 0;
230              
231 0         0  
232 0         0 eval {
  0         0  
233 0 0       0 foreach my $parser ( @{ $dbh->{mock_parser} } ) {
234 0         0 if (ref($parser) eq 'CODE') {
235             $parser->($statement);
236             }
237 0         0 else {
238             $parser->parse($statement);
239             }
240             }
241 0 0       0 };
242 0         0 if ($@) {
243 0         0 my $parser_error = $@;
244 0         0 chomp $parser_error;
245 0         0 $dbh->DBI::set_err(1, "Failed to parse statement. Error: ${parser_error}. Statement: ${statement}");
246             return;
247             }
248 0 0       0  
249 0         0 if (my $session = $dbh->FETCH('mock_session')) {
250 0         0 eval {
251             $session->verify_statement($dbh, $statement);
252 0 0       0 };
253 0         0 if ($@) {
254 0         0 my $session_error = $@;
255 0         0 chomp $session_error;
256 0         0 $dbh->DBI::set_err(1, "Session Error: ${session_error}. Statement: ${statement}");
257             return;
258             }
259             }
260 0         0  
261             my $sth = DBI::_new_sth($dbh, { Statement => $statement });
262 0         0  
263             $sth->trace_msg("Preparing statement '${statement}'\n", 1);
264 0         0  
265             my %track_params = (statement => $statement);
266              
267             # If we have available resultsets seed the tracker with one
268 0         0  
269 0 0       0 my $rs;
270 0 0       0 if ( my $all_rs = $dbh->{mock_rs} ) {
271             if ( my $by_name = $all_rs->{named}{$statement} ) {
272 0         0 # We want to copy this, because it is meant to be reusable
  0         0  
273 0 0       0 $rs = [ @{$by_name->{results}} ];
274 0         0 if (exists $by_name->{failure}) {
  0         0  
275             $track_params{failure} = [ @{$by_name->{failure}} ];
276             }
277             }
278 0         0 else {
  0         0  
279             $rs = shift @{$all_rs->{ordered}};
280             }
281             }
282 0 0 0     0  
  0         0  
283 0         0 if (ref($rs) eq 'ARRAY' && scalar(@{$rs}) > 0 ) {
  0         0  
284 0         0 my $fields = shift @{$rs};
285 0         0 $track_params{return_data} = $rs;
286 0         0 $track_params{fields} = $fields;
287 0         0 $sth->STORE(NAME => $fields);
  0         0  
288             $sth->STORE(NUM_OF_FIELDS => scalar @{$fields});
289             }
290 0         0 else {
291             $sth->trace_msg("No return data set in DBH\n", 1);
292             }
293              
294             # do not allow a statement handle to be created if there is no
295             # connection present.
296 0 0       0  
297 0         0 unless ($dbh->FETCH('Active')) {
298 0         0 $dbh->DBI::set_err(1, "No connection present");
299             return;
300             }
301              
302             # This history object will track everything done to the statement
303 0         0  
304 0         0 my $history = DBD::Mock::StatementTrack->new(%track_params);
305             $sth->STORE(mock_my_history => $history);
306              
307             # ...now associate the history object with the database handle so
308             # people can browse the entire history at once, even for
309             # statements opened and closed in a black box
310 0         0  
311 0         0 my $all_history = $dbh->FETCH('mock_statement_history');
  0         0  
312             push @{$all_history}, $history;
313 0         0  
314             return $sth;
315             }
316              
317             *prepare_cached = \&prepare;
318              
319             {
320             my $begin_work_commit;
321 0     0 0 0 sub begin_work {
322 0 0       0 my $dbh = shift;
323 0         0 if ($dbh->FETCH('AutoCommit')) {
324 0         0 $dbh->STORE('AutoCommit', 0);
325 0         0 $begin_work_commit = 1;
326 0         0 my $sth = $dbh->prepare( 'BEGIN WORK' );
327 0         0 my $rc = $sth->execute();
328 0         0 $sth->finish();
329             return $rc;
330             }
331 0         0 else {
332             return $dbh->set_err(1, 'AutoCommit is off, you are already within a transaction');
333             }
334             }
335              
336 0     0 0 0 sub commit {
337 0 0 0     0 my $dbh = shift;
338 0         0 if ($dbh->FETCH('AutoCommit') && $dbh->FETCH('Warn')) {
339             return $dbh->set_err(1, "commit ineffective with AutoCommit" );
340             }
341 0         0  
342 0         0 my $sth = $dbh->prepare( 'COMMIT' );
343 0         0 my $rc = $sth->execute();
344             $sth->finish();
345 0 0       0  
346 0         0 if ($begin_work_commit) {
347 0         0 $dbh->STORE('AutoCommit', 1);
348             $begin_work_commit = 0;
349             }
350 0         0  
351             return $rc;
352             }
353              
354 0     0 0 0 sub rollback {
355 0 0 0     0 my $dbh = shift;
356 0         0 if ($dbh->FETCH('AutoCommit') && $dbh->FETCH('Warn')) {
357             return $dbh->set_err(1, "rollback ineffective with AutoCommit" );
358             }
359 0         0  
360 0         0 my $sth = $dbh->prepare( 'ROLLBACK' );
361 0         0 my $rc = $sth->execute();
362             $sth->finish();
363 0 0       0  
364 0         0 if ($begin_work_commit) {
365 0         0 $dbh->STORE('AutoCommit', 1);
366             $begin_work_commit = 0;
367             }
368 0         0  
369             return $rc;
370             }
371             }
372              
373             # NOTE:
374             # this method should work in most cases, however it does
375             # not exactly follow the DBI spec in the case of error
376             # handling. I am not sure if that level of detail is
377             # really nessecary since it is a weird error conditon
378             # which causes it to fail anyway. However if you find you do need it,
379             # then please email me about it. I think it would be possible
380             # to mimic it by accessing the DBD::Mock::StatementTrack
381             # object directly.
382 0     0 0 0 sub selectcol_arrayref {
383             my ($dbh, $query, $attrib, @bindvalues) = @_;
384 0         0 # get all the columns ...
385             my $a_ref = $dbh->selectall_arrayref($query, $attrib, @bindvalues);
386              
387             # if we get nothing back, or dont get an
388             # ARRAY ref back, then we can assume
389 0 0 0     0 # something went wrong, and so return undef.
390             return undef unless defined $a_ref || ref($a_ref) ne 'ARRAY';
391 0         0  
392 0 0       0 my @cols = 0;
393 0         0 if (ref $attrib->{Columns} eq 'ARRAY') {
  0         0  
  0         0  
394             @cols = map { $_ - 1 } @{$attrib->{Columns}};
395             }
396              
397             # if we do get something then we
398 0         0 # grab all the columns out of it.
  0         0  
  0         0  
399             return [ map { @$_[@cols] } @{$a_ref} ]
400             }
401              
402 0     0   0 sub FETCH {
403 0         0 my ( $dbh, $attrib, $value ) = @_;
404             $dbh->trace_msg( "Fetching DB attrib '$attrib'\n" );
405 0 0       0  
    0          
    0          
    0          
    0          
406 0         0 if ($attrib eq 'Active') {
407             return $dbh->{mock_can_connect};
408             }
409 0         0 elsif ($attrib eq 'mock_all_history') {
410             return $dbh->{mock_statement_history};
411             }
412 0         0 elsif ($attrib eq 'mock_all_history_iterator') {
413             return DBD::Mock::StatementTrack::Iterator->new($dbh->{mock_statement_history});
414             }
415 0         0 elsif ($attrib =~ /^mock/) {
416             return $dbh->{$attrib};
417             }
418 0         0 elsif ($attrib =~ /^(private_|dbi_|dbd_|[A-Z])/ ) {
419 0         0 $dbh->trace_msg("... fetching non-driver attribute ($attrib) that DBI handles\n");
420             return $dbh->SUPER::FETCH($attrib);
421             }
422 0 0       0 else {
423 0 0       0 if ($dbh->{mock_attribute_aliases}) {
  0         0  
424 0         0 if (exists ${$dbh->{mock_attribute_aliases}->{db}}{$attrib}) {
425 0 0       0 my $mock_attrib = $dbh->{mock_attribute_aliases}->{db}->{$attrib};
426 0         0 if (ref($mock_attrib) eq 'CODE') {
427             return $mock_attrib->($dbh);
428             }
429 0         0 else {
430             return $dbh->FETCH($mock_attrib);
431             }
432             }
433 0         0 }
434 0         0 $dbh->trace_msg( "... fetching non-driver attribute ($attrib) that DBI doesn't handle\n");
435             return $dbh->{$attrib};
436             }
437             }
438              
439 10     10   456 sub STORE {
440 10 50       66 my ( $dbh, $attrib, $value ) = @_;
441             $dbh->trace_msg( "Storing DB attribute '$attrib' with '" . (defined($value) ? $value : 'undef') . "'\n" );
442 10 100       24  
443             if ($attrib eq 'AutoCommit') {
444             # These are magic DBI values that say we can handle AutoCommit
445 1 50       4 # internally as well
446             $value = ($value) ? -901 : -900;
447             }
448 10 50       77  
    50          
    50          
    50          
    50          
    50          
    100          
    50          
449 0 0       0 if ( $attrib eq 'mock_clear_history' ) {
450 0         0 if ( $value ) {
451             $dbh->{mock_statement_history} = [];
452 0         0 }
453             return [];
454             }
455 0         0 elsif ( $attrib eq 'mock_add_parser' ) {
456 0         0 my $parser_type = ref($value);
457             my $is_valid_parser;
458 0 0 0     0  
    0          
459 0         0 if ($parser_type eq 'CODE') {
460             $is_valid_parser++;
461             }
462 0         0 elsif ($parser_type && $parser_type !~ /^(ARRAY|HASH|SCALAR)$/) {
  0         0  
463             $is_valid_parser = eval { $parser_type->can( 'parse' ) };
464             }
465 0 0       0  
466 0         0 unless ($is_valid_parser) {
467             my $error = "Parser must be a code reference or object with 'parse()' " .
468 0         0 "method (Given type: '$parser_type')";
469 0         0 $dbh->DBI::set_err(1, $error);
470             return;
471 0         0 }
  0         0  
472 0         0 push @{$dbh->{mock_parser}}, $value;
473             return $value;
474             }
475 0   0     0 elsif ( $attrib eq 'mock_add_resultset' ) {
476             $dbh->{mock_rs} ||= { named => {},
477 0 0       0 ordered => [] };
    0          
478 0         0 if ( ref $value eq 'ARRAY' ) {
  0         0  
479 0         0 my @copied_values = @{$value};
  0         0  
480 0         0 push @{$dbh->{mock_rs}{ordered}}, \@copied_values;
481             return \@copied_values;
482             }
483 0         0 elsif ( ref $value eq 'HASH' ) {
484 0 0       0 my $name = $value->{sql};
485 0         0 unless ($name) {
486             die "Indexing resultset by name requires passing in 'sql' ",
487             "as hashref key to 'mock_add_resultset'.\n";
488 0         0 }
  0         0  
489 0         0 my @copied_values = @{$value->{results}};
490             $dbh->{mock_rs}{named}{$name} = {
491             results => \@copied_values,
492 0 0       0 };
493 0         0 if ( exists $value->{failure} ) {
494 0         0 $dbh->{mock_rs}{named}{$name}{failure} = [
495             @{$value->{failure}},
496             ];
497 0         0 }
498             return \@copied_values;
499             }
500 0         0 else {
501             die "Must provide an arrayref or hashref when adding ",
502             "resultset via 'mock_add_resultset'.\n";
503             }
504             }
505 0 0       0 elsif ($attrib eq 'mock_start_insert_id') {
506 0 0       0 if ( ref $value eq 'ARRAY' ) {
507 0         0 $dbh->{mock_last_insert_ids} = {} unless $dbh->{mock_last_insert_ids};
508             $dbh->{mock_last_insert_ids}{$value->[0]} = $value->[1];
509             }
510             else {
511             # we start at one minus the start id
512 0         0 # so that the increment works
513             $dbh->{mock_last_insert_id} = $value - 1;
514             }
515              
516             }
517 0 0 0     0 elsif ($attrib eq 'mock_session') {
      0        
518             (ref($value) && UNIVERSAL::isa($value, 'DBD::Mock::Session'))
519             || die "Only DBD::Mock::Session objects can be placed into the 'mock_session' slot\n"
520 0         0 if defined $value;
521             $dbh->{mock_session} = $value;
522             }
523 0         0 elsif ($attrib =~ /^mock_(add_)?data_sources/) {
524             $dbh->{Driver}->STORE($attrib, $value);
525             }
526 6         45 elsif ($attrib =~ /^mock/) {
527             return $dbh->{$attrib} = $value;
528             }
529 4         21 elsif ($attrib =~ /^(private_|dbi_|dbd_|[A-Z])/ ) {
530 4         173 $dbh->trace_msg("... storing non-driver attribute ($attrib) with value ($value) that DBI handles\n");
531             return $dbh->SUPER::STORE($attrib, $value);
532             }
533 0         0 else {
534 0         0 $dbh->trace_msg("... storing non-driver attribute ($attrib) with value ($value) that DBI won't handle\n");
535             return $dbh->{$attrib} = $value;
536             }
537             }
538              
539 1     1   84 sub DESTROY {
540 1 50       153 my ($dbh) = @_;
541 0 0         if ( my $session = $dbh->{mock_session} ) {
542 0           if ( $session->has_states_left ) {
543             die "DBH->finish called when session still has states left\n";
544             }
545             }
546             }
547              
548 0     0 0   sub disconnect {
549 0 0         my ($dbh) = @_;
550 0 0         if ( my $session = $dbh->{mock_session} ) {
551 0           if ( $session->has_states_left ) {
552             die "DBH->finish called when session still has states left\n";
553             }
554             }
555             }
556              
557             ########################################
558             # STATEMENT
559              
560             package
561             DBD::Mock::st;
562 1     1   8  
  1         1  
  1         31  
563 1     1   5 use strict;
  1         2  
  1         2470  
564             use warnings;
565              
566             $DBD::Mock::st::imp_data_size = 0;
567              
568 0     0 0   sub bind_col {
569             my ($sth, $param_num, $ref, $attr) = @_;
570 0            
571 0           my $tracker = $sth->FETCH( 'mock_my_history' );
572 0           $tracker->bind_col( $param_num, $ref );
573             return 1;
574             }
575              
576 0     0 0   sub bind_param {
577 0           my ($sth, $param_num, $val, $attr) = @_;
578 0           my $tracker = $sth->FETCH( 'mock_my_history' );
579 0           $tracker->bound_param( $param_num, $val );
580             return 1;
581             }
582              
583 0     0 0   sub bind_param_inout {
584             my ($sth, $param_num, $val, $max_len) = @_;
585 0 0         # check that $val is a scalar ref
586             (UNIVERSAL::isa($val, 'SCALAR'))
587             || $sth->{Database}->DBI::set_err(1, "need a scalar ref to bind_param_inout, not $val");
588 0 0         # check for positive $max_len
589             ($max_len > 0)
590 0           || $sth->{Database}->DBI::set_err(1, "need to specify a maximum length to bind_param_inout");
591 0           my $tracker = $sth->FETCH( 'mock_my_history' );
592 0           $tracker->bound_param( $param_num, $val );
593             return 1;
594             }
595              
596 0     0 0   sub execute {
597 0           my ($sth, @params) = @_;
598             my $dbh = $sth->{Database};
599 0 0          
600 0           unless ($dbh->{mock_can_connect}) {
601 0           $dbh->DBI::set_err(1, "No connection present");
602             return 0;
603 0 0         }
604 0           unless ($dbh->{mock_can_execute}) {
605 0           $dbh->DBI::set_err(1, "Cannot execute");
606             return 0;
607 0 0         }
608             $dbh->{mock_can_execute}++ if $dbh->{mock_can_execute} < 0;
609 0            
610             my $tracker = $sth->FETCH( 'mock_my_history' );
611 0 0          
612 0           if ($tracker->has_failure()) {
613 0           $dbh->DBI::set_err($tracker->get_failure());
614             return 0;
615             }
616 0 0          
617 0           if ( @params ) {
618             $tracker->bind_params( @params );
619             }
620 0 0          
621 0           if (my $session = $dbh->{mock_session}) {
622 0           eval {
623 0           $session->verify_bound_params($dbh, $tracker->bound_params());
624 0           my $idx = $session->{state_index} - 1;
  0            
625 0           my @results = @{$session->{states}->[$idx]->{results}};
626 0           shift @results;
627             $tracker->{return_data} = \@results;
628 0 0         };
629 0           if ($@) {
630 0           my $session_error = $@;
631 0           chomp $session_error;
632 0           $dbh->DBI::set_err(1, "Session Error: ${session_error}");
633             return;
634             }
635             }
636 0            
637 0           $tracker->mark_executed;
638 0           my $fields = $tracker->fields;
639             $sth->STORE( NUM_OF_PARAMS => $tracker->num_params );
640              
641             # handle INSERT statements and the mock_last_insert_ids
642             # We should only increment these things after the last successful INSERT.
643             # -RobK, 2007-10-12
644 0 0         #use Data::Dumper;warn Dumper $dbh->{mock_last_insert_ids};
645 0 0 0       if ($dbh->{Statement} =~ /^\s*?insert\s+into\s+(\S+)/i) {
646 0           if ( $dbh->{mock_last_insert_ids} && exists $dbh->{mock_last_insert_ids}{$1} ) {
647             $dbh->{mock_last_insert_id} = $dbh->{mock_last_insert_ids}{$1}++;
648             }
649 0           else {
650             $dbh->{mock_last_insert_id}++;
651             }
652             }
653             #warn "$dbh->{mock_last_insert_id}\n";
654              
655 0 0         # always return 0E0 for Selects
656 0           if ($dbh->{Statement} =~ /^\s*?select/i) {
657             return '0E0';
658 0   0       }
659             return ($sth->rows() || '0E0');
660             }
661              
662 0     0 0   sub fetch {
663 0           my ($sth) = @_;
664 0 0         my $dbh = $sth->{Database};
665 0           unless ($dbh->{mock_can_connect}) {
666 0           $dbh->DBI::set_err(1, "No connection present");
667             return;
668 0 0         }
669 0           unless ($dbh->{mock_can_fetch}) {
670 0           $dbh->DBI::set_err(1, "Cannot fetch");
671             return;
672 0 0         }
673             $dbh->{mock_can_fetch}++ if $dbh->{mock_can_fetch} < 0;
674 0            
675             my $tracker = $sth->FETCH( 'mock_my_history' );
676 0 0          
677             my $record = $tracker->next_record
678             or return;
679 0 0          
680 0           if ( my @cols = $tracker->bind_cols() ) {
  0            
681 0           for my $i ( grep { ref $cols[$_] } 0..$#cols ) {
  0            
682             ${ $cols[$i] } = $record->[$i];
683             }
684             }
685 0            
686             return $record;
687             }
688              
689 0     0 0   sub fetchrow_array {
690 0           my ($sth) = @_;
691 0 0         my $row = $sth->DBD::Mock::st::fetch();
692 0           return unless ref($row) eq 'ARRAY';
  0            
693             return @{$row};
694             }
695              
696 0     0 0   sub fetchrow_arrayref {
697 0           my ($sth) = @_;
698             return $sth->DBD::Mock::st::fetch();
699             }
700              
701 0     0 0   sub fetchrow_hashref {
702 0           my ($sth, $name) = @_;
703             my $dbh = $sth->{Database};
704             # handle any errors since we are grabbing
705 0 0         # from the tracker directly
706 0           unless ($dbh->{mock_can_connect}) {
707 0           $dbh->DBI::set_err(1, "No connection present");
708             return;
709 0 0         }
710 0           unless ($dbh->{mock_can_fetch}) {
711 0           $dbh->DBI::set_err(1, "Cannot fetch");
712             return;
713 0 0         }
714             $dbh->{mock_can_fetch}++ if $dbh->{mock_can_fetch} < 0;
715              
716 0   0       # first handle the $name, it will default to NAME
717             $name ||= 'NAME';
718 0           # then fetch the names from the $sth (per DBI spec)
719             my $fields = $sth->FETCH($name);
720              
721 0           # now check the tracker ...
722             my $tracker = $sth->FETCH( 'mock_my_history' );
723 0 0         # and collect the results
724 0           if (my $record = $tracker->next_record()) {
  0            
725             my @values = @{$record};
726 0           return {
727 0           map {
728 0           $_ => shift(@values)
729             } @{$fields}
730             };
731             }
732 0            
733             return undef;
734             }
735              
736             #XXX Isn't this supposed to return an array of hashrefs? -RobK, 2007-10-15
737 0     0 0   sub fetchall_hashref {
738 0           my ($sth, $keyfield) = @_;
739             my $dbh = $sth->{Database};
740             # handle any errors since we are grabbing
741 0 0         # from the tracker directly
742 0           unless ($dbh->{mock_can_connect}) {
743 0           $dbh->DBI::set_err(1, "No connection present");
744             return;
745 0 0         }
746 0           unless ($dbh->{mock_can_fetch}) {
747 0           $dbh->DBI::set_err(1, "Cannot fetch");
748             return;
749 0 0         }
750             $dbh->{mock_can_fetch}++ if $dbh->{mock_can_fetch} < 0;
751 0            
752 0           my $tracker = $sth->FETCH( 'mock_my_history' );
753             my $rethash = {};
754              
755 0   0       # get the name set by
756 0           my $name = $sth->{Database}->FETCH('FetchHashKeyName') || 'NAME';
757             my $fields = $sth->FETCH($name);
758              
759 0 0         # check if $keyfield is not an integer
760 0           if (!($keyfield =~ /^-?\d+$/)) {
761             my $found = 0;
762 0           # search for index of item that matches $keyfield
  0            
763 0 0         foreach my $index (0 .. scalar(@{$fields})) {
764 0           if ($fields->[$index] eq $keyfield) {
765             $found++;
766 0           # now make the keyfield the index
767             $keyfield = $index;
768 0           # and jump out of the loop :)
769             last;
770             }
771 0 0         }
772 0           unless ($found) {
773 0           $dbh->DBI::set_err(1, "Could not find key field '$keyfield'");
774             return;
775             }
776             }
777              
778 0           # now loop through all the records ...
779             while (my $record = $tracker->next_record()) {
780             # copy the values so as to preserve
781 0           # the original record...
  0            
782             my @values = @{$record};
783 0           # populate the hash
784             $rethash->{$record->[$keyfield]} = {
785 0           map {
786 0           $_ => shift(@values)
787             } @{$fields}
788             };
789             }
790 0            
791             return $rethash;
792             }
793              
794 0     0 0   sub finish {
795 0           my ($sth) = @_;
796             $sth->FETCH( 'mock_my_history' )->is_finished( 'yes' );
797             }
798              
799 0     0 0   sub rows {
800 0           my ($sth) = @_;
801             $sth->FETCH('mock_num_rows');
802             }
803              
804 0     0     sub FETCH {
805 0           my ( $sth, $attrib ) = @_;
806 0           $sth->trace_msg( "Fetching ST attribute '$attrib'\n" );
807 0           my $tracker = $sth->{mock_my_history};
808             $sth->trace_msg( "Retrieved tracker: " . ref( $tracker ) . "\n" );
809 0 0         # NAME attributes
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
810 0           if ( $attrib eq 'NAME' ) {
  0            
811             return [ @{$tracker->fields} ];
812             }
813 0           elsif ( $attrib eq 'NAME_lc' ) {
  0            
  0            
814             return [ map { lc($_) } @{$tracker->fields} ];
815             }
816 0           elsif ( $attrib eq 'NAME_uc' ) {
  0            
  0            
817             return [ map { uc($_) } @{$tracker->fields} ];
818             }
819             # NAME_hash attributes
820 0           elsif ( $attrib eq 'NAME_hash' ) {
821 0           my $i = 0;
  0            
  0            
822             return { map { $_ => $i++ } @{$tracker->fields} };
823             }
824 0           elsif ( $attrib eq 'NAME_hash_lc' ) {
825 0           my $i = 0;
  0            
  0            
826             return { map { lc($_) => $i++ } @{$tracker->fields} };
827             }
828 0           elsif ( $attrib eq 'NAME_hash_uc' ) {
829 0           my $i = 0;
  0            
  0            
830             return { map { uc($_) => $i++ } @{$tracker->fields} };
831             }
832             # others
833 0           elsif ( $attrib eq 'NUM_OF_FIELDS' ) {
834             return $tracker->num_fields;
835             }
836 0           elsif ( $attrib eq 'NUM_OF_PARAMS' ) {
837             return $tracker->num_params;
838             }
839 0           elsif ( $attrib eq 'TYPE' ) {
840 0           my $num_fields = $tracker->num_fields;
  0            
841             return [ map { $DBI::SQL_VARCHAR } ( 0 .. $num_fields ) ];
842             }
843 0           elsif ( $attrib eq 'Active' ) {
844             return $tracker->is_active;
845             }
846 0 0         elsif ( $attrib !~ /^mock/ ) {
847 0 0         if ($sth->{Database}->{mock_attribute_aliases}) {
  0            
848 0           if (exists ${$sth->{Database}->{mock_attribute_aliases}->{st}}{$attrib}) {
849 0 0         my $mock_attrib = $sth->{Database}->{mock_attribute_aliases}->{st}->{$attrib};
850 0           if (ref($mock_attrib) eq 'CODE') {
851             return $mock_attrib->($sth);
852             }
853 0           else {
854             return $sth->FETCH($mock_attrib);
855             }
856             }
857 0           }
858             return $sth->SUPER::FETCH( $attrib );
859             }
860              
861             # now do our stuff...
862 0 0          
863 0           if ( $attrib eq 'mock_my_history' ) {
864             return $tracker;
865 0 0 0       }
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
866 0           if ( $attrib eq 'mock_statement' ) {
867             return $tracker->statement;
868             }
869 0           elsif ( $attrib eq 'mock_params' ) {
870             return $tracker->bound_params;
871             }
872 0           elsif ( $attrib eq 'mock_records' ) {
873             return $tracker->return_data;
874             }
875 0           elsif ( $attrib eq 'mock_num_records' || $attrib eq 'mock_num_rows' ) {
876             return $tracker->num_rows;
877             }
878 0           elsif ( $attrib eq 'mock_current_record_num' ) {
879             return $tracker->current_record_num;
880             }
881 0           elsif ( $attrib eq 'mock_fields' ) {
882             return $tracker->fields;
883             }
884 0           elsif ( $attrib eq 'mock_is_executed' ) {
885             return $tracker->is_executed;
886             }
887 0           elsif ( $attrib eq 'mock_is_finished' ) {
888             return $tracker->is_finished;
889             }
890 0           elsif ( $attrib eq 'mock_is_depleted' ) {
891             return $tracker->is_depleted;
892             }
893 0           else {
894             die "I don't know how to retrieve statement attribute '$attrib'\n";
895             }
896             }
897              
898 0     0     sub STORE {
899 0           my ($sth, $attrib, $value) = @_;
900 0 0         $sth->trace_msg( "Storing ST attribute '$attrib'\n" );
    0          
901 0           if ($attrib =~ /^mock/) {
902             return $sth->{$attrib} = $value;
903             }
904             elsif ($attrib =~ /^NAME/) {
905 0           # no-op...
906             return;
907             }
908 0   0       else {
909 0           $value ||= 0;
910             return $sth->SUPER::STORE( $attrib, $value );
911             }
912             }
913 0     0      
914             sub DESTROY { undef }
915              
916             ##########################
917             # Database Pooling
918             # (Apache::DBI emulation)
919              
920             package
921             DBD::Mock::Pool;
922 1     1   11  
  1         2  
  1         29  
923 1     1   9 use strict;
  1         3  
  1         111  
924             use warnings;
925              
926             my $connection;
927              
928 0 0   0 0   sub connect {
929             return $connection if $connection;
930              
931             # according to the code before my tweaks, this could be a class
932 0 0         # name, but it was never used - DR, 2008-11-08
933             shift unless ref $_[0];
934 0            
935 0           my $drh = shift;
936             return $connection = bless $drh->connect(@_), 'DBD::Mock::Pool::db';
937             }
938              
939             package
940             DBD::Mock::Pool::db;
941 1     1   5  
  1         2  
  1         34  
942 1     1   5 use strict;
  1         1  
  1         98  
943             use warnings;
944              
945             our @ISA = qw(DBI::db);
946 0     0 0    
947             sub disconnect { 1 }
948              
949             ########################################
950             # TRACKER
951              
952             package
953             DBD::Mock::StatementTrack;
954 1     1   5  
  1         1  
  1         38  
955 1     1   6 use strict;
  1         1  
  1         1487  
956             use warnings;
957              
958 0     0 0   sub new {
959             my ($class, %params) = @_;
960             # these params have default values
961 0   0       # but can be overridden
962 0   0       $params{return_data} ||= [];
963 0   0       $params{fields} ||= [];
964 0   0       $params{bound_params} ||= [];
965 0   0       $params{statement} ||= "";
966             $params{failure} ||= undef;
967             # these params should never be overridden
968             # and should always start out in a default
969 0           # state to assure the sanity of this class
970 0           $params{is_executed} = 'no';
971 0           $params{is_finished} = 'no';
972             $params{current_record_num} = 0;
973             # NOTE:
974             # changed from \%params here because that
975             # would bind the hash sent in so that it
976             # would reflect alterations in the object
977 0           # this violates encapsulation
978 0           my $self = bless { %params }, $class;
979             return $self;
980             }
981              
982 0     0 0   sub has_failure {
983 0 0         my ($self) = @_;
984             $self->{failure} ? 1 : 0;
985             }
986              
987 0     0 0   sub get_failure {
988 0           my ($self) = @_;
  0            
989             @{$self->{failure}};
990             }
991              
992 0     0 0   sub num_fields {
993 0           my ($self) = @_;
  0            
994             return scalar @{$self->{fields}};
995             }
996              
997 0     0 0   sub num_rows {
998 0           my ($self) = @_;
  0            
999             return scalar @{$self->{return_data}};
1000             }
1001              
1002 0     0 0   sub num_params {
1003 0           my ($self) = @_;
  0            
1004             return scalar @{$self->{bound_params}};
1005             }
1006              
1007 0     0 0   sub bind_col {
1008 0           my ($self, $param_num, $ref) = @_;
1009             $self->{bind_cols}->[$param_num - 1] = $ref;
1010             }
1011              
1012 0     0 0   sub bound_param {
1013 0           my ($self, $param_num, $value) = @_;
1014 0           $self->{bound_params}->[$param_num - 1] = $value;
1015             return $self->bound_params;
1016             }
1017              
1018 0     0 0   sub bound_param_trailing {
1019 0           my ($self, @values) = @_;
  0            
1020             push @{$self->{bound_params}}, @values;
1021             }
1022              
1023 0     0 0   sub bind_cols {
1024 0 0         my $self = shift;
  0            
1025             return @{$self->{bind_cols} || []};
1026             }
1027              
1028 0     0 0   sub bind_params {
1029 0           my ($self, @values) = @_;
  0            
1030             @{$self->{bound_params}} = @values;
1031             }
1032              
1033             # Rely on the DBI's notion of Active: a statement is active if it's
1034             # currently in a SELECT and has more records to fetch
1035              
1036 0     0 0   sub is_active {
1037 0 0         my ($self) = @_;
1038 0 0         return 0 unless $self->statement =~ /^\s*select/ism;
1039 0 0         return 0 unless $self->is_executed eq 'yes';
1040 0           return 0 if $self->is_depleted;
1041             return 1;
1042             }
1043              
1044 0     0 0   sub is_finished {
1045 0 0 0       my ($self, $value) = @_;
    0          
1046 0           if (defined $value && $value eq 'yes' ) {
1047 0           $self->{is_finished} = 'yes';
1048 0           $self->current_record_num(0);
1049             $self->{return_data} = [];
1050             }
1051 0           elsif (defined $value) {
1052             $self->{is_finished} = 'no';
1053 0           }
1054             return $self->{is_finished};
1055             }
1056              
1057             ####################
1058             # RETURN VALUES
1059              
1060 0     0 0   sub mark_executed {
1061 0           my ($self) = @_;
1062 0           $self->is_executed('yes');
1063             $self->current_record_num(0);
1064             }
1065              
1066 0     0 0   sub next_record {
1067 0 0         my ($self) = @_;
1068 0           return if $self->is_depleted;
1069 0           my $rec_num = $self->current_record_num;
1070 0           my $rec = $self->return_data->[$rec_num];
1071 0           $self->current_record_num($rec_num + 1);
1072             return $rec;
1073             }
1074              
1075 0     0 0   sub is_depleted {
1076 0           my ($self) = @_;
  0            
1077             return ($self->current_record_num >= scalar @{$self->return_data});
1078             }
1079              
1080             # DEBUGGING AID
1081              
1082 0     0 0   sub to_string {
1083 0           my ($self) = @_;
1084             return join "\n" => (
1085 0           $self->{statement},
1086 0           "Values: [" . join( '] [', @{ $self->{bound_params} } ) . "]",
1087             "Records: on $self->{current_record_num} of " . scalar(@{$self->return_data}) . "\n",
1088             "Executed? $self->{is_executed}; Finished? $self->{is_finished}"
1089             );
1090             }
1091              
1092             # PROPERTIES
1093              
1094             # boolean
1095              
1096 0     0 0   sub is_executed {
1097 0 0         my ($self, $yes_no) = @_;
1098 0 0         $self->{is_executed} = $yes_no if defined $yes_no;
1099             return ($self->{is_executed} eq 'yes') ? 'yes' : 'no';
1100             }
1101              
1102             # single-element fields
1103              
1104 0     0 0   sub statement {
1105 0 0         my ($self, $value) = @_;
1106 0           $self->{statement} = $value if defined $value;
1107             return $self->{statement};
1108             }
1109              
1110 0     0 0   sub current_record_num {
1111 0 0         my ($self, $value) = @_;
1112 0           $self->{current_record_num} = $value if defined $value;
1113             return $self->{current_record_num};
1114             }
1115              
1116             # multi-element fields
1117              
1118 0     0 0   sub return_data {
1119 0 0         my ($self, @values) = @_;
  0            
1120 0           push @{$self->{return_data}}, @values if scalar @values;
1121             return $self->{return_data};
1122             }
1123              
1124 0     0 0   sub fields {
1125 0 0         my ($self, @values) = @_;
  0            
1126 0           push @{$self->{fields}}, @values if scalar @values;
1127             return $self->{fields};
1128             }
1129              
1130 0     0 0   sub bound_params {
1131 0 0         my ($self, @values) = @_;
  0            
1132 0           push @{$self->{bound_params}}, @values if scalar @values;
1133             return $self->{bound_params};
1134             }
1135              
1136             package
1137             DBD::Mock::StatementTrack::Iterator;
1138 1     1   5  
  1         2  
  1         28  
1139 1     1   4 use strict;
  1         2  
  1         181  
1140             use warnings;
1141              
1142 0     0 0   sub new {
1143 0   0       my ($class, $history) = @_;
1144             return bless {
1145             pointer => 0,
1146             history => $history || []
1147             } => $class;
1148             }
1149              
1150 0     0 0   sub next {
1151 0 0         my ($self) = @_;
  0            
1152 0           return unless $self->{pointer} < scalar(@{$self->{history}});
1153             return $self->{history}->[$self->{pointer}++];
1154             }
1155 0     0 0    
1156             sub reset { (shift)->{pointer} = 0 }
1157              
1158             package
1159             DBD::Mock::Session;
1160 1     1   5  
  1         2  
  1         28  
1161 1     1   4 use strict;
  1         2  
  1         711  
1162             use warnings;
1163              
1164             my $INSTANCE_COUNT = 1;
1165              
1166 0     0 0   sub new {
1167 0 0         my $class = shift;
1168 0           (@_) || die "You must specify at least one session state";
1169 0 0         my $session_name;
1170 0           if (ref($_[0])) {
1171             $session_name = 'Session ' . $INSTANCE_COUNT;
1172             }
1173 0           else {
1174             $session_name = shift;
1175 0           }
1176 0 0         my @session_states = @_;
1177             (@session_states)
1178             || die "You must specify at least one session state";
1179             (ref($_) eq 'HASH')
1180 0   0       || die "You must specify session states as HASH refs"
1181 0           foreach @session_states;
1182 0           $INSTANCE_COUNT++;
1183             return bless {
1184             name => $session_name,
1185             states => \@session_states,
1186             state_index => 0
1187             } => $class;
1188             }
1189 0     0 0    
1190 0     0 0   sub name { (shift)->{name} }
1191 0     0 0   sub reset { (shift)->{state_index} = 0 }
  0            
1192             sub num_states { scalar( @{ (shift)->{states} } ) }
1193              
1194 0     0 0   sub has_states_left {
1195 0           my $self = shift;
  0            
1196             return $self->{state_index} < scalar(@{$self->{states}});
1197             }
1198              
1199 0     0 0   sub verify_statement {
1200             my ($self, $dbh, $statement) = @_;
1201 0            
1202 0 0         ($self->has_states_left)
1203             || die "Session states exhausted, only '" . scalar(@{$self->{states}}) . "' in DBD::Mock::Session (" . $self->{name} . ")";
1204 0            
1205             my $current_state = $self->{states}->[$self->{state_index}];
1206 0 0 0       # make sure our state is good
  0            
  0            
1207             (exists ${$current_state}{statement} && exists ${$current_state}{results})
1208             || die "Bad state '" . $self->{state_index} . "' in DBD::Mock::Session (" . $self->{name} . ")";
1209 0           # try the SQL
1210 0 0         my $SQL = $current_state->{statement};
    0          
    0          
1211 0 0         unless (ref($SQL)) {
1212             ($SQL eq $statement)
1213             || die "Statement does not match current state in DBD::Mock::Session (" . $self->{name} . ")\n" .
1214             " got: $statement\n" .
1215             " expected: $SQL";
1216             }
1217 0 0         elsif (ref($SQL) eq 'Regexp') {
1218             ($statement =~ /$SQL/)
1219             || die "Statement does not match current state (with Regexp) in DBD::Mock::Session (" . $self->{name} . ")\n" .
1220             " got: $statement\n" .
1221             " expected: $SQL";
1222             }
1223 0 0         elsif (ref($SQL) eq 'CODE') {
1224             ($SQL->($statement, $current_state))
1225             || die "Statement does not match current state (with CODE ref) in DBD::Mock::Session (" . $self->{name} . ")";
1226             }
1227 0           else {
1228             die "Bad 'statement' value '$SQL' in current state in DBD::Mock::Session (" . $self->{name} . ")";
1229             }
1230             # copy the result sets so that
1231 0           # we can re-use the session
  0            
1232             $dbh->STORE('mock_add_resultset' => [ @{$current_state->{results}} ]);
1233             }
1234              
1235 0     0 0   sub verify_bound_params {
1236 0           my ($self, $dbh, $params) = @_;
1237 0 0         my $current_state = $self->{states}->[$self->{state_index}];
  0            
1238 0           if (exists ${$current_state}{bound_params}) {
1239 0           my $expected = $current_state->{bound_params};
  0            
  0            
1240             (scalar(@{$expected}) == scalar(@{$params}))
1241 0           || die "Not the same number of bound params in current state in DBD::Mock::Session (" . $self->{name} . ")\n" .
1242 0 0         " got: " . scalar(@{$params}) . "\n" .
1243 0           " expected: " . scalar(@{$expected});
  0            
1244 1     1   7 for (my $i = 0; $i < scalar(@{$params}); $i++) {
  1         2  
  1         224  
1245 0 0         no warnings;
1246 0 0         if (ref($expected->[$i]) eq 'Regexp') {
1247             ($params->[$i] =~ /$expected->[$i]/)
1248             || die "Bound param $i do not match (using regexp) in current state in DBD::Mock::Session (" . $self->{name} . ")\n" .
1249             " got: " . $params->[$i] . "\n" .
1250             " expected: " . $expected->[$i];
1251             }
1252 0 0         else {
1253             ($params->[$i] eq $expected->[$i])
1254             || die "Bound param $i do not match in current state in DBD::Mock::Session (" . $self->{name} . ")\n" .
1255             " got: " . $params->[$i] . "\n" .
1256             " expected: " . $expected->[$i];
1257             }
1258             }
1259             }
1260             # and make sure we go to
1261 0           # the next statement
1262             $self->{state_index}++;
1263             }
1264              
1265             1;
1266              
1267             __END__