File Coverage

blib/lib/DBD/Mock/db.pm
Criterion Covered Total %
statement 229 238 96.2
branch 134 152 88.1
condition 26 38 68.4
subroutine 18 18 100.0
pod 0 10 0.0
total 407 456 89.2


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