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   275 use strict;
  40         70  
  40         1207  
4 40     40   210 use warnings;
  40         78  
  40         1108  
5              
6 40     40   196 use List::Util qw( first );
  40         83  
  40         2070  
7 40     40   323 use DBI;
  40         98  
  40         113802  
8              
9             our $imp_data_size = 0;
10              
11             sub ping {
12 8     8 0 1064 my ($dbh) = @_;
13 8         41 return $dbh->{mock_can_connect};
14             }
15              
16             sub last_insert_id {
17 14     14 0 2971 my ($dbh) = @_;
18 14         50 return $dbh->{mock_last_insert_id};
19             }
20              
21             sub get_info {
22 1     1 0 11 my ( $dbh, $attr ) = @_;
23 1   50     5 $dbh->{mock_get_info} ||= {};
24 1         6 return $dbh->{mock_get_info}{$attr};
25             }
26              
27             sub table_info {
28 5     5 0 1902 my ( $dbh, @params ) = @_;
29              
30 5 100       17 my ($cataloge, $schema, $table, $type) = map { $_ || '' } @params[0..4];
  25         70  
31              
32 5   100     18 $dbh->{mock_table_info} ||= {};
33              
34 5 100       5 my @tables = @{ $dbh->{mock_table_info}->{ $cataloge }->{ $schema }->{ $table }->{ $type } || [] };
  5         69  
35              
36 5         14 my ($fieldNames, @rows) = map { [ @$_ ] } @tables;
  8         34  
37              
38 5   100     17 $fieldNames ||= [];
39              
40 5 50       20 my $sponge = DBI->connect('dbi:Sponge:', '', '' )
41             or return $dbh->DBI::set_err($DBI::err, "DBI::Sponge: $DBI::errstr");
42              
43 5 50       3384 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         528 return $sth;
50             }
51              
52             sub prepare {
53 150     150 0 28797 my ( $dbh, $statement ) = @_;
54              
55 150 100       453 unless ( $dbh->{mock_can_connect} ) {
56 2         22 $dbh->set_err( 1, "No connection present" );
57 2         35 return;
58             }
59 148 100       444 unless ( $dbh->{mock_can_prepare} ) {
60 2         10 $dbh->set_err( 1, "Cannot prepare" );
61 2         16 return;
62             }
63 146 100       675 $dbh->{mock_can_prepare}++ if $dbh->{mock_can_prepare} < 0;
64              
65 146         277 eval {
66 146         239 foreach my $parser ( @{ $dbh->{mock_parser} } )
  146         416  
67             {
68 4 100       12 if ( ref($parser) eq 'CODE' ) {
69 2         4 $parser->($statement);
70             }
71             else {
72 2         6 $parser->parse($statement);
73             }
74             }
75             };
76 146 100       399 if ($@) {
77 2         5 my $parser_error = $@;
78 2         4 chomp $parser_error;
79 2         25 $dbh->set_err( 1,
80             "Failed to parse statement. Error: ${parser_error}. Statement: ${statement}"
81             );
82 2         37 return;
83             }
84              
85 144         661 my $sth = DBI::_new_sth( $dbh, { Statement => $statement } );
86 144         5225 $sth->trace_msg( "Preparing statement '${statement}'\n", 1 );
87 144         448 my %track_params = ( statement => $statement );
88              
89 144 100       423 if ( my $session = $dbh->{mock_session} ) {
90 46         67 eval {
91 46         151 my $rs = $session->results_for($statement);
92 41 100 66     168 if ( ref($rs) eq 'ARRAY' && scalar( @{$rs} ) > 0 ) {
  41         128  
93 29         45 my $fields = @{$rs}[0];
  29         44  
94 29         69 $track_params{return_data} = $rs;
95 29         50 $track_params{fields} = $fields;
96 29         168 $sth->STORE( NAME => $fields );
97 29         51 $sth->STORE( NUM_OF_FIELDS => scalar @{$fields} );
  29         96  
98             }
99             else {
100 12         40 $sth->trace_msg( "No return data set in DBH\n", 1 );
101             }
102             };
103              
104 46 100       130 if ($@) {
105 5         41 $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 98         192 my ($rs, $callback, $failure, $prepare_attributes, $execute_attributes);
114              
115 98 100       295 if ( my $all_rs = $dbh->{mock_rs} ) {
116 59 100   4   284 if ( my $by_name = defined $all_rs->{named}{$statement} ? $all_rs->{named}{$statement} : first { $statement =~ m/$_->{regexp}/ } @{ $all_rs->{matching} } ) {
  4 100       35  
  14         134  
117             # We want to copy this, because it is meant to be reusable
118 48         85 $rs = [ @{ $by_name->{results} } ];
  48         116  
119 48         90 $callback = $by_name->{callback};
120 48         73 $failure = $by_name->{failure};
121 48         70 $prepare_attributes = $by_name->{prepare_attributes};
122 48         76 $execute_attributes = $by_name->{execute_attributes};
123             }
124             else {
125 11         20 $rs = shift @{ $all_rs->{ordered} };
  11         29  
126 11 100       55 if (ref($rs) eq 'HASH') {
127 1         2 $callback = $rs->{callback};
128 1         2 $failure = $rs->{failure};
129 1         2 $prepare_attributes = $rs->{prepare_attributes};
130 1         3 $execute_attributes = $rs->{execute_attributes};
131 1         2 $rs = [ @{ $rs->{results} } ];
  1         4  
132             }
133             }
134             }
135              
136 98 100 66     390 if ( ref($rs) eq 'ARRAY' && ( scalar( @{$rs} ) > 0 || $callback ) ) {
      66        
137 56         94 my $fields = shift @{$rs};
  56         97  
138 56         111 $track_params{return_data} = $rs;
139 56         106 $track_params{fields} = $fields;
140 56         89 $track_params{callback} = $callback;
141 56         87 $track_params{failure} = $failure;
142 56         83 $track_params{driver_attributes} = $prepare_attributes;
143 56         90 $track_params{execute_attributes} = $execute_attributes;
144              
145 56 100       101 if( $fields ) {
146 52         304 $sth->STORE( NAME => $fields );
147 52         83 $sth->STORE( NUM_OF_FIELDS => scalar @{$fields});
  52         176  
148             }
149              
150             }
151             else {
152 42         144 $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 144 50       742 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 144         902 my $history = DBD::Mock::StatementTrack->new(%track_params);
167 144         728 $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 144         652 my $all_history = $dbh->FETCH('mock_statement_history');
174 144         244 push @{$all_history}, $history;
  144         321  
175              
176 144         615 return $sth;
177             }
178              
179             *prepare_cached = \&prepare;
180              
181             {
182             my $begin_work_commit;
183              
184             sub begin_work {
185 6     6 0 205 my $dbh = shift;
186 6 100       22 if ( $dbh->FETCH('AutoCommit') ) {
187 5         19 $dbh->STORE( 'AutoCommit', 0 );
188 5         9 $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       19 my $rc = $sth->execute()
192             or return $dbh->set_err( 1, $DBI::errstr );
193 4         19 $sth->finish();
194 4         23 return $rc;
195             }
196             else {
197 1         32 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 20 my $dbh = shift;
204 4 100 66     18 if ( $dbh->FETCH('AutoCommit') && $dbh->FETCH('Warn') ) {
205 1         12 return $dbh->set_err( 1, "commit ineffective with AutoCommit" );
206             }
207              
208 3 50       14 my $sth = $dbh->prepare('COMMIT')
209             or return $dbh->set_err( 1, $DBI::errstr );
210 3 100       29 my $rc = $sth->execute()
211             or return $dbh->set_err( 1, $DBI::errstr );
212 2         11 $sth->finish();
213              
214 2 100       5 if ($begin_work_commit) {
215 1         5 $dbh->STORE( 'AutoCommit', 1 );
216 1         2 $begin_work_commit = 0;
217             }
218              
219 2         14 return $rc;
220             }
221              
222             sub rollback {
223 4     4 0 10 my $dbh = shift;
224 4 100 66     15 if ( $dbh->FETCH('AutoCommit') && $dbh->FETCH('Warn') ) {
225 1         19 return $dbh->set_err( 1, "rollback ineffective with AutoCommit" );
226             }
227              
228 3 50       14 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         18 $sth->finish();
233              
234 2 100       14 if ($begin_work_commit) {
235 1         6 $dbh->STORE( 'AutoCommit', 1 );
236 1         2 $begin_work_commit = 0;
237             }
238              
239 2         13 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 1654 my ( $dbh, $query, $attrib, @bindvalues ) = @_;
254              
255             # get all the columns ...
256 2         13 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     11 return undef unless defined $a_ref || ref($a_ref) ne 'ARRAY';
262              
263 2         4 my @cols = 0;
264 2 100       8 if ( ref $attrib->{Columns} eq 'ARRAY' ) {
265 1         3 @cols = map { $_ - 1 } @{ $attrib->{Columns} };
  2         5  
  1         3  
266             }
267              
268             # if we do get something then we
269             # grab all the columns out of it.
270 2         4 return [ map { @$_[@cols] } @{$a_ref} ];
  4         17  
  2         5  
271             }
272              
273             sub FETCH {
274 227     227   17767 my ( $dbh, $attrib, $value ) = @_;
275 227         936 $dbh->trace_msg("Fetching DB attrib '$attrib'\n");
276              
277 227 100       965 if ( $attrib eq 'Active' ) {
    100          
    100          
    100          
    100          
278 152         607 return $dbh->{mock_can_connect};
279             }
280             elsif ( $attrib eq 'mock_all_history' ) {
281 12         49 return $dbh->{mock_statement_history};
282             }
283             elsif ( $attrib eq 'mock_all_history_iterator' ) {
284             return DBD::Mock::StatementTrack::Iterator->new(
285 1         5 $dbh->{mock_statement_history} );
286             }
287             elsif ( $attrib =~ /^mock/ ) {
288 6         45 return $dbh->{$attrib};
289             }
290             elsif ( $attrib =~ /^(private_|dbi_|dbd_|[A-Z])/ ) {
291 50         422 $dbh->trace_msg(
292             "... fetching non-driver attribute ($attrib) that DBI handles\n");
293 50         349 return $dbh->SUPER::FETCH($attrib);
294             }
295             else {
296 6 50       14 if ( $dbh->{mock_attribute_aliases} ) {
297 6 50       10 if ( exists ${ $dbh->{mock_attribute_aliases}->{db} }{$attrib} ) {
  6         29  
298             my $mock_attrib =
299 6         13 $dbh->{mock_attribute_aliases}->{db}->{$attrib};
300 6 50       11 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 1010     1010   50214 my ( $dbh, $attrib, $value ) = @_;
317              
318 1010   100     2418 my $printed_value = $value || 'undef';
319 1010         4095 $dbh->trace_msg("Storing DB attribute '$attrib' with '$printed_value'\n");
320              
321 1010 100       2136 if ( $attrib eq 'AutoCommit' ) {
322              
323             # These are magic DBI values that say we can handle AutoCommit
324             # internally as well
325 99 100       256 $value = ($value) ? -901 : -900;
326             }
327              
328 1010 100       5941 if ( $attrib eq 'mock_clear_history' ) {
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
329 5 50       22 if ($value) {
330 5         12 $dbh->{mock_statement_history} = [];
331             }
332 5         20 return [];
333             }
334             elsif ( $attrib eq 'mock_add_parser' ) {
335 6         14 my $parser_type = ref($value);
336 6         8 my $is_valid_parser;
337              
338 6 100 100     78 if ( $parser_type eq 'CODE' ) {
    100          
339 1         3 $is_valid_parser++;
340             }
341             elsif ( $parser_type && $parser_type !~ /^(ARRAY|HASH|SCALAR)$/ ) {
342 1         3 $is_valid_parser = eval { $parser_type->can('parse') };
  1         11  
343             }
344              
345 6 100       15 unless ($is_valid_parser) {
346 4         12 my $error =
347             "Parser must be a code reference or object with 'parse()' "
348             . "method (Given type: '$parser_type')";
349 4         28 $dbh->set_err( 1, $error );
350 4         52 return;
351             }
352 2         4 push @{ $dbh->{mock_parser} }, $value;
  2         8  
353 2         9 return $value;
354             }
355             elsif ( $attrib eq 'mock_add_resultset' ) {
356 34         66 my @copied_values;
357              
358             $dbh->{mock_rs} ||= {
359 34   100     216 named => {},
360             ordered => [],
361             matching => [],
362             };
363              
364 34 100       153 if ( ref $value eq 'ARRAY' ) {
    50          
365 7         135 @copied_values = @{$value};
  7         28  
366 7         19 push @{ $dbh->{mock_rs}{ordered} }, \@copied_values;
  7         25  
367             }
368             elsif ( ref $value eq 'HASH' ) {
369 27         275 my $name = $value->{sql};
370              
371 27 100       100 @copied_values = @{ $value->{results} ? $value->{results} : [] };
  27         132  
372              
373 27 100       119 if (not defined $name) {
    100          
374 1         7 push @{ $dbh->{mock_rs}{ordered} }, {
375             results => \@copied_values,
376             callback => $value->{callback},
377 1         7 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         8 failure => ref($value->{failure}) ? [ @{ $value->{failure} } ] : undef,
388             prepare_attributes => $value->{prepare_attributes},
389             execute_attributes => $value->{execute_attributes},
390 4 100       26 };
391             # either replace existing match or push
392 3 100       20 grep { $_->{regexp} eq $name && ($_ = $matching) } @{ $dbh->{mock_rs}{matching} }
  4         17  
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         7 failure => ref($value->{failure}) ? [ @{ $value->{failure} } ] : undef,
400             prepare_attributes => $value->{prepare_attributes},
401             execute_attributes => $value->{execute_attributes},
402 22 100       152 };
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 34         163 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       10 unless $dbh->{mock_last_insert_ids};
416 3         14 $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     194 ( 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         231 $dbh->{mock_session} = $value;
432             }
433             elsif ( $attrib =~ /^mock_(add_)?data_sources/ ) {
434 1         12 $dbh->{Driver}->STORE( $attrib, $value );
435             }
436             elsif ( $attrib =~ /^mock_add_table_info$/ ) {
437 4   50     14 $dbh->{mock_table_info} ||= {};
438              
439 4 50       11 if ( ref $value ne "HASH" ) {
440 0         0 die "mock_add_table_info needs a hash reference"
441             }
442              
443 4 100       9 my ( $cataloge, $schema, $table, $type ) = map { defined $_ ? $_ : '' } @$value{qw( cataloge schema table type )};
  16         38  
444              
445 4         17 $dbh->{mock_table_info}->{ $cataloge }->{ $schema }->{ $table }->{ $type } = $value->{table_info};
446             }
447             elsif ( $attrib =~ /^mock_clear_table_info$/ ) {
448 1 50       5 if ( $value ) {
449 1         5 $dbh->{mock_table_info} = {};
450             }
451              
452 1         6 return {};
453             }
454             elsif ( $attrib =~ /^mock/ ) {
455 525         2100 return $dbh->{$attrib} = $value;
456             }
457             elsif ( $attrib =~ /^(private_|dbi_|dbd_|[A-Z])/ ) {
458 398         1580 $dbh->trace_msg(
459             "... storing non-driver attribute ($attrib) with value ($printed_value) that DBI handles\n"
460             );
461 398         2953 return $dbh->SUPER::STORE( $attrib, $value );
462             }
463             else {
464 2         14 $dbh->trace_msg(
465             "... storing non-driver attribute ($attrib) with value ($printed_value) that DBI won't handle\n"
466             );
467 2         10 return $dbh->{$attrib} = $value;
468             }
469             }
470              
471             sub DESTROY {
472 79     79   21255 my ($dbh) = @_;
473 79 100       4396 if ( my $session = $dbh->{mock_session} ) {
474 13 50       43 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 6400 my ($dbh) = @_;
482 11 100       102 if ( my $session = $dbh->{mock_session} ) {
483 1 50       4 if ( $session->has_states_left ) {
484 1         6 die "DBH->finish called when session still has states left\n";
485             }
486             }
487             }
488              
489             1;