File Coverage

blib/lib/DBD/Mock/db.pm
Criterion Covered Total %
statement 235 244 96.3
branch 134 152 88.1
condition 26 38 68.4
subroutine 18 18 100.0
pod 0 10 0.0
total 413 462 89.3


line stmt bran cond sub pod time code
1             package DBD::Mock::db;
2              
3 40     40   290 use strict;
  40         88  
  40         1286  
4 40     40   211 use warnings;
  40         78  
  40         1259  
5              
6 40     40   207 use List::Util qw( first );
  40         74  
  40         2311  
7 40     40   353 use DBI;
  40         108  
  40         120268  
8              
9             our $imp_data_size = 0;
10              
11             sub ping {
12 8     8 0 892 my ($dbh) = @_;
13 8         39 return $dbh->{mock_can_connect};
14             }
15              
16             sub last_insert_id {
17 14     14 0 3590 my ($dbh) = @_;
18 14         61 return $dbh->{mock_last_insert_id};
19             }
20              
21             sub get_info {
22 1     1 0 13 my ( $dbh, $attr ) = @_;
23 1   50     5 $dbh->{mock_get_info} ||= {};
24 1         65 return $dbh->{mock_get_info}{$attr};
25             }
26              
27             sub table_info {
28 5     5 0 1572 my ( $dbh, @params ) = @_;
29              
30 5 100       16 my ($cataloge, $schema, $table, $type) = map { $_ || '' } @params[0..4];
  25         115  
31              
32 5   100     17 $dbh->{mock_table_info} ||= {};
33              
34 5 100       8 my @tables = @{ $dbh->{mock_table_info}->{ $cataloge }->{ $schema }->{ $table }->{ $type } || [] };
  5         21  
35              
36 5         9 my ($fieldNames, @rows) = map { [ @$_ ] } @tables;
  8         18  
37              
38 5   100     15 $fieldNames ||= [];
39              
40 5 50       22 my $sponge = DBI->connect('dbi:Sponge:', '', '' )
41             or return $dbh->DBI::set_err($DBI::err, "DBI::Sponge: $DBI::errstr");
42              
43 5 50       3361 my $sth = $sponge->prepare("table_info", {
44             rows => \@rows,
45             NUM_OF_FIELDS => scalar @$fieldNames,
46             NAME => $fieldNames
47             }) or return $dbh->DBI::set_err( $sponge->err(), $sponge->errstr() );
48              
49 5         569 return $sth;
50             }
51              
52             sub prepare {
53 151     151 0 26202 my ( $dbh, $statement ) = @_;
54              
55 151 100       481 unless ( $dbh->{mock_can_connect} ) {
56 2         31 $dbh->set_err( 1, "No connection present" );
57 2         54 return;
58             }
59 149 100       401 unless ( $dbh->{mock_can_prepare} ) {
60 2         10 $dbh->set_err( 1, "Cannot prepare" );
61 2         18 return;
62             }
63 147 100       407 $dbh->{mock_can_prepare}++ if $dbh->{mock_can_prepare} < 0;
64              
65 147         269 eval {
66 147         226 foreach my $parser ( @{ $dbh->{mock_parser} } )
  147         461  
67             {
68 4 100       26 if ( ref($parser) eq 'CODE' ) {
69 2         5 $parser->($statement);
70             }
71             else {
72 2         7 $parser->parse($statement);
73             }
74             }
75             };
76 147 100       416 if ($@) {
77 2         4 my $parser_error = $@;
78 2         5 chomp $parser_error;
79 2         23 $dbh->set_err( 1,
80             "Failed to parse statement. Error: ${parser_error}. Statement: ${statement}"
81             );
82 2         35 return;
83             }
84              
85 145         720 my $sth = DBI::_new_sth( $dbh, { Statement => $statement } );
86 145         5710 $sth->trace_msg( "Preparing statement '${statement}'\n", 1 );
87 145         454 my %track_params = ( statement => $statement );
88              
89 145 100       569 if ( my $session = $dbh->{mock_session} ) {
90 46         91 eval {
91 46         185 my $rs = $session->results_for($statement);
92 41 100 66     187 if ( ref($rs) eq 'ARRAY' && scalar( @{$rs} ) > 0 ) {
  41         138  
93 29         48 my $fields = @{$rs}[0];
  29         55  
94 29         57 $track_params{return_data} = $rs;
95 29         69 $track_params{fields} = $fields;
96 29         212 $sth->STORE( NAME => $fields );
97 29         63 $sth->STORE( NUM_OF_FIELDS => scalar @{$fields} );
  29         96  
98             }
99             else {
100 12         42 $sth->trace_msg( "No return data set in DBH\n", 1 );
101             }
102             };
103              
104 46 100       144 if ($@) {
105 5         40 $dbh->DBI::set_err( 1, "Session Error: $@. Statement: $statement" );
106             }
107              
108             }
109              
110             else {
111             # If we have available resultsets seed the tracker with one
112              
113 99         193 my ($rs, $callback, $failure, $prepare_attributes, $execute_attributes);
114              
115 99 100       294 if ( my $all_rs = $dbh->{mock_rs} ) {
116 60 100   4   281 if ( my $by_name = defined $all_rs->{named}{$statement} ? $all_rs->{named}{$statement} : first { $statement =~ m/$_->{regexp}/ } @{ $all_rs->{matching} } ) {
  4 100       33  
  14         129  
117             # We want to copy this, because it is meant to be reusable
118 49         73 $rs = [ @{ $by_name->{results} } ];
  49         121  
119 49         97 $callback = $by_name->{callback};
120 49         83 $failure = $by_name->{failure};
121 49         67 $prepare_attributes = $by_name->{prepare_attributes};
122 49         90 $execute_attributes = $by_name->{execute_attributes};
123             }
124             else {
125 11         23 $rs = shift @{ $all_rs->{ordered} };
  11         38  
126 11 100       48 if (ref($rs) eq 'HASH') {
127 1         3 $callback = $rs->{callback};
128 1         2 $failure = $rs->{failure};
129 1         2 $prepare_attributes = $rs->{prepare_attributes};
130 1         2 $execute_attributes = $rs->{execute_attributes};
131 1         1 $rs = [ @{ $rs->{results} } ];
  1         4  
132             }
133             }
134             }
135              
136 99 100 66     418 if ( ref($rs) eq 'ARRAY' && ( scalar( @{$rs} ) > 0 || $callback ) ) {
      66        
137 57         88 my $fields = shift @{$rs};
  57         110  
138 57         119 $track_params{return_data} = $rs;
139 57         104 $track_params{fields} = $fields;
140 57         103 $track_params{callback} = $callback;
141 57         99 $track_params{failure} = $failure;
142 57         97 $track_params{driver_attributes} = $prepare_attributes;
143 57         98 $track_params{execute_attributes} = $execute_attributes;
144              
145 57 100       159 if( $fields ) {
146 52         275 $sth->STORE( NAME => $fields );
147 52         97 $sth->STORE( NUM_OF_FIELDS => scalar @{$fields});
  52         199  
148             }
149              
150             }
151             else {
152 42         153 $sth->trace_msg( "No return data set in DBH\n", 1 );
153             }
154              
155             }
156              
157             # do not allow a statement handle to be created if there is no
158             # connection present.
159              
160 145 50       894 unless ( $dbh->FETCH('Active') ) {
161 0         0 $dbh->set_err( 1, "No connection present" );
162 0         0 return;
163             }
164              
165             # This history object will track everything done to the statement
166 145         904 my $history = DBD::Mock::StatementTrack->new(%track_params);
167 145         789 $sth->STORE( mock_my_history => $history );
168              
169             # ...now associate the history object with the database handle so
170             # people can browse the entire history at once, even for
171             # statements opened and closed in a black box
172              
173 145         588 my $all_history = $dbh->FETCH('mock_statement_history');
174 145         250 push @{$all_history}, $history;
  145         342  
175              
176 145         637 return $sth;
177             }
178              
179             *prepare_cached = \&prepare;
180              
181             {
182             my $begin_work_commit;
183              
184             sub begin_work {
185 6     6 0 210 my $dbh = shift;
186 6 100       23 if ( $dbh->FETCH('AutoCommit') ) {
187 5         20 $dbh->STORE( 'AutoCommit', 0 );
188 5         11 $begin_work_commit = 1;
189 5 50       18 my $sth = $dbh->prepare('BEGIN WORK')
190             or return $dbh->set_err( 1, $DBI::errstr );
191 5 100       34 my $rc = $sth->execute()
192             or return $dbh->set_err( 1, $DBI::errstr );
193 4         19 $sth->finish();
194 4         22 return $rc;
195             }
196             else {
197 1         30 return $dbh->set_err( 1,
198             'AutoCommit is off, you are already within a transaction' );
199             }
200             }
201              
202             sub commit {
203 4     4 0 11 my $dbh = shift;
204 4 100 66     16 if ( $dbh->FETCH('AutoCommit') && $dbh->FETCH('Warn') ) {
205 1         13 return $dbh->set_err( 1, "commit ineffective with AutoCommit" );
206             }
207              
208 3 50       33 my $sth = $dbh->prepare('COMMIT')
209             or return $dbh->set_err( 1, $DBI::errstr );
210 3 100       16 my $rc = $sth->execute()
211             or return $dbh->set_err( 1, $DBI::errstr );
212 2         12 $sth->finish();
213              
214 2 100       6 if ($begin_work_commit) {
215 1         5 $dbh->STORE( 'AutoCommit', 1 );
216 1         3 $begin_work_commit = 0;
217             }
218              
219 2         13 return $rc;
220             }
221              
222             sub rollback {
223 4     4 0 11 my $dbh = shift;
224 4 100 66     16 if ( $dbh->FETCH('AutoCommit') && $dbh->FETCH('Warn') ) {
225 1         30 return $dbh->set_err( 1, "rollback ineffective with AutoCommit" );
226             }
227              
228 3 50       16 my $sth = $dbh->prepare('ROLLBACK')
229             or return $dbh->set_err( 1, $DBI::errstr );
230 3 100       14 my $rc = $sth->execute()
231             or return $dbh->set_err( 1, $DBI::errstr );
232 2         8 $sth->finish();
233              
234 2 100       5 if ($begin_work_commit) {
235 1         5 $dbh->STORE( 'AutoCommit', 1 );
236 1         2 $begin_work_commit = 0;
237             }
238              
239 2         11 return $rc;
240             }
241             }
242              
243             # NOTE:
244             # this method should work in most cases, however it does
245             # not exactly follow the DBI spec in the case of error
246             # handling. I am not sure if that level of detail is
247             # really nessecary since it is a weird error conditon
248             # which causes it to fail anyway. However if you find you do need it,
249             # then please email me about it. I think it would be possible
250             # to mimic it by accessing the DBD::Mock::StatementTrack
251             # object directly.
252             sub selectcol_arrayref {
253 2     2 0 1541 my ( $dbh, $query, $attrib, @bindvalues ) = @_;
254              
255             # get all the columns ...
256 2         16 my $a_ref = $dbh->selectall_arrayref( $query, $attrib, @bindvalues );
257              
258             # if we get nothing back, or dont get an
259             # ARRAY ref back, then we can assume
260             # something went wrong, and so return undef.
261 2 50 33     24 return undef unless defined $a_ref || ref($a_ref) ne 'ARRAY';
262              
263 2         5 my @cols = 0;
264 2 100       8 if ( ref $attrib->{Columns} eq 'ARRAY' ) {
265 1         2 @cols = map { $_ - 1 } @{ $attrib->{Columns} };
  2         5  
  1         4  
266             }
267              
268             # if we do get something then we
269             # grab all the columns out of it.
270 2         3 return [ map { @$_[@cols] } @{$a_ref} ];
  4         20  
  2         5  
271             }
272              
273             sub FETCH {
274 228     228   17719 my ( $dbh, $attrib, $value ) = @_;
275 228         1036 $dbh->trace_msg("Fetching DB attrib '$attrib'\n");
276              
277 228 100       960 if ( $attrib eq 'Active' ) {
    100          
    100          
    100          
    100          
278 153         641 return $dbh->{mock_can_connect};
279             }
280             elsif ( $attrib eq 'mock_all_history' ) {
281 12         51 return $dbh->{mock_statement_history};
282             }
283             elsif ( $attrib eq 'mock_all_history_iterator' ) {
284             return DBD::Mock::StatementTrack::Iterator->new(
285 1         4 $dbh->{mock_statement_history} );
286             }
287             elsif ( $attrib =~ /^mock/ ) {
288 6         36 return $dbh->{$attrib};
289             }
290             elsif ( $attrib =~ /^(private_|dbi_|dbd_|[A-Z])/ ) {
291 50         229 $dbh->trace_msg(
292             "... fetching non-driver attribute ($attrib) that DBI handles\n");
293 50         399 return $dbh->SUPER::FETCH($attrib);
294             }
295             else {
296 6 50       15 if ( $dbh->{mock_attribute_aliases} ) {
297 6 50       9 if ( exists ${ $dbh->{mock_attribute_aliases}->{db} }{$attrib} ) {
  6         18  
298             my $mock_attrib =
299 6         11 $dbh->{mock_attribute_aliases}->{db}->{$attrib};
300 6 50       12 if ( ref($mock_attrib) eq 'CODE' ) {
301 0         0 return $mock_attrib->($dbh);
302             }
303             else {
304 6         31 return $dbh->FETCH($mock_attrib);
305             }
306             }
307             }
308             $dbh->trace_msg(
309 0         0 "... fetching non-driver attribute ($attrib) that DBI doesn't handle\n"
310             );
311 0         0 return $dbh->{$attrib};
312             }
313             }
314              
315             sub STORE {
316 1011     1011   40525 my ( $dbh, $attrib, $value ) = @_;
317              
318 1011   100     2412 my $printed_value = $value || 'undef';
319 1011         4175 $dbh->trace_msg("Storing DB attribute '$attrib' with '$printed_value'\n");
320              
321 1011 100       2186 if ( $attrib eq 'AutoCommit' ) {
322              
323             # These are magic DBI values that say we can handle AutoCommit
324             # internally as well
325 99 100       296 $value = ($value) ? -901 : -900;
326             }
327              
328 1011 100       6225 if ( $attrib eq 'mock_clear_history' ) {
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
329 5 50       22 if ($value) {
330 5         16 $dbh->{mock_statement_history} = [];
331             }
332 5         21 return [];
333             }
334             elsif ( $attrib eq 'mock_add_parser' ) {
335 6         13 my $parser_type = ref($value);
336 6         7 my $is_valid_parser;
337              
338 6 100 100     46 if ( $parser_type eq 'CODE' ) {
    100          
339 1         2 $is_valid_parser++;
340             }
341             elsif ( $parser_type && $parser_type !~ /^(ARRAY|HASH|SCALAR)$/ ) {
342 1         2 $is_valid_parser = eval { $parser_type->can('parse') };
  1         8  
343             }
344              
345 6 100       13 unless ($is_valid_parser) {
346 4         13 my $error =
347             "Parser must be a code reference or object with 'parse()' "
348             . "method (Given type: '$parser_type')";
349 4         29 $dbh->set_err( 1, $error );
350 4         56 return;
351             }
352 2         3 push @{ $dbh->{mock_parser} }, $value;
  2         7  
353 2         9 return $value;
354             }
355             elsif ( $attrib eq 'mock_add_resultset' ) {
356 35         438 my @copied_values;
357              
358             $dbh->{mock_rs} ||= {
359 35   100     280 named => {},
360             ordered => [],
361             matching => [],
362             };
363              
364 35 100       145 if ( ref $value eq 'ARRAY' ) {
    50          
365 7         15 @copied_values = @{$value};
  7         37  
366 7         17 push @{ $dbh->{mock_rs}{ordered} }, \@copied_values;
  7         26  
367             }
368             elsif ( ref $value eq 'HASH' ) {
369 28         52 my $name = $value->{sql};
370              
371 28 100       39 @copied_values = @{ $value->{results} ? $value->{results} : [] };
  28         99  
372              
373 28 100       105 if (not defined $name) {
    100          
374 1         7 push @{ $dbh->{mock_rs}{ordered} }, {
375             results => \@copied_values,
376             callback => $value->{callback},
377 1         6 failure => ref($value->{failure}) ? [ @{ $value->{failure} } ] : undef,
378             prepare_attributes => $value->{prepare_attributes},
379             execute_attributes => $value->{execute_attributes},
380 1 50       2 };
381             }
382             elsif ( ref $name eq "Regexp" ) {
383             my $matching = {
384             regexp => $name,
385             results => \@copied_values,
386             callback => $value->{callback},
387 1         6 failure => ref($value->{failure}) ? [ @{ $value->{failure} } ] : undef,
388             prepare_attributes => $value->{prepare_attributes},
389             execute_attributes => $value->{execute_attributes},
390 4 100       22 };
391             # either replace existing match or push
392 3 100       18 grep { $_->{regexp} eq $name && ($_ = $matching) } @{ $dbh->{mock_rs}{matching} }
  4         14  
393 4 100       7 or push @{ $dbh->{mock_rs}{matching} }, $matching;
  3         9  
394             }
395             else {
396             $dbh->{mock_rs}{named}{$name} = {
397             results => \@copied_values,
398             callback => $value->{callback},
399 1         6 failure => ref($value->{failure}) ? [ @{ $value->{failure} } ] : undef,
400             prepare_attributes => $value->{prepare_attributes},
401             execute_attributes => $value->{execute_attributes},
402 23 100       216 };
403             }
404             }
405             else {
406 0         0 die "Must provide an arrayref or hashref when adding ",
407             "resultset via 'mock_add_resultset'.\n";
408             }
409              
410 35         173 return \@copied_values;
411             }
412             elsif ( $attrib eq 'mock_start_insert_id' ) {
413 3 50       11 if ( ref $value eq 'ARRAY' ) {
414             $dbh->{mock_last_insert_ids} = {}
415 3 100       13 unless $dbh->{mock_last_insert_ids};
416 3         53 $dbh->{mock_last_insert_ids}{ $value->[0] } = $value->[1];
417             }
418             else {
419              
420             # we start at one minus the start id
421             # so that the increment works
422 0         0 $dbh->{mock_last_insert_id} = $value - 1;
423             }
424              
425             }
426             elsif ( $attrib eq 'mock_session' ) {
427 31 100 33     244 ( ref($value) && UNIVERSAL::isa( $value, 'DBD::Mock::Session' ) )
      50        
428             || die
429             "Only DBD::Mock::Session objects can be placed into the 'mock_session' slot\n"
430             if defined $value;
431 31         225 $dbh->{mock_session} = $value;
432             }
433             elsif ( $attrib =~ /^mock_(add_)?data_sources/ ) {
434 1         13 $dbh->{Driver}->STORE( $attrib, $value );
435             }
436             elsif ( $attrib =~ /^mock_add_table_info$/ ) {
437 4   50     12 $dbh->{mock_table_info} ||= {};
438              
439 4 50       10 if ( ref $value ne "HASH" ) {
440 0         0 die "mock_add_table_info needs a hash reference"
441             }
442              
443 4 100       11 my ( $cataloge, $schema, $table, $type ) = map { defined $_ ? $_ : '' } @$value{qw( cataloge schema table type )};
  16         33  
444              
445 4         18 $dbh->{mock_table_info}->{ $cataloge }->{ $schema }->{ $table }->{ $type } = $value->{table_info};
446             }
447             elsif ( $attrib =~ /^mock_clear_table_info$/ ) {
448 1 50       4 if ( $value ) {
449 1         5 $dbh->{mock_table_info} = {};
450             }
451              
452 1         4 return {};
453             }
454             elsif ( $attrib =~ /^mock/ ) {
455 525         2134 return $dbh->{$attrib} = $value;
456             }
457             elsif ( $attrib =~ /^(private_|dbi_|dbd_|[A-Z])/ ) {
458 398         1702 $dbh->trace_msg(
459             "... storing non-driver attribute ($attrib) with value ($printed_value) that DBI handles\n"
460             );
461 398         3097 return $dbh->SUPER::STORE( $attrib, $value );
462             }
463             else {
464 2         13 $dbh->trace_msg(
465             "... storing non-driver attribute ($attrib) with value ($printed_value) that DBI won't handle\n"
466             );
467 2         8 return $dbh->{$attrib} = $value;
468             }
469             }
470              
471             sub DESTROY {
472 79     79   20581 my ($dbh) = @_;
473 79 100       4623 if ( my $session = $dbh->{mock_session} ) {
474 13 50       78 if ( $session->has_states_left ) {
475 0         0 die "DBH->finish called when session still has states left\n";
476             }
477             }
478             }
479              
480             sub disconnect {
481 11     11 0 6485 my ($dbh) = @_;
482 11 100       113 if ( my $session = $dbh->{mock_session} ) {
483 1 50       4 if ( $session->has_states_left ) {
484 1         5 die "DBH->finish called when session still has states left\n";
485             }
486             }
487             }
488              
489             1;