File Coverage

blib/lib/DBI/Fetch.pm
Criterion Covered Total %
statement 84 197 42.6
branch 35 148 23.6
condition 13 54 24.0
subroutine 14 19 73.6
pod 4 10 40.0
total 150 428 35.0


line stmt bran cond sub pod time code
1             package DBI::Fetch;
2              
3             # Copyright (c) 2012 Iain Campbell. All rights reserved.
4             #
5             # This work may be used and modified freely, but I ask that the copyright
6             # notice remain attached to the file. You may modify this module as you
7             # wish, but if you redistribute a modified version, please attach a note
8             # listing the modifications you have made.
9              
10             BEGIN {
11 1     1   27060 $DBI::Fetch::AUTHORITY = 'cpan:CPANIC';
12 1         3 $DBI::Fetch::VERSION = '1.00';
13 1         51 $DBI::Fetch::VERSION = eval $DBI::Fetch::VERSION;
14             }
15              
16 1     1   38 use 5.008_004;
  1         4  
  1         48  
17 1     1   10 use strict;
  1         3  
  1         44  
18 1     1   6 use warnings::register;
  1         2  
  1         195  
19 1     1   6 use warnings 'all';
  1         1  
  1         43  
20 1     1   5 use base 'Exporter';
  1         3  
  1         160  
21              
22 1     1   2696 use DBI;
  1         23848  
  1         91  
23              
24 1         80 use Params::Callbacks qw/
25             callbacks
26 1     1   1829 /;
  1         698  
27              
28 1         3029 use Scalar::Util qw/
29             blessed
30             reftype
31 1     1   7 /;
  1         1  
32              
33             our @EXPORT;
34              
35             our @EXPORT_OK = qw/
36             process
37             /;
38              
39             our %EXPORT_TAGS = (
40             default => \@EXPORT,
41             all => \@EXPORT_OK,
42             );
43              
44             our $E_UNIMP_METHOD = 'Method "%s" not implemented by package "%s"';
45             our $E_NO_DBH = 'Database handle expected';
46             our $E_NO_SQL = 'SQL statement expected';
47             our $E_EXP_L_REF = 'Hash or array reference expected';
48             our $E_EXP_A_REF = 'Array reference expected';
49             our $E_EXP_H_REF = 'Hash reference expected';
50             our $E_EXP_STH = 'Statement handle expected but got %s object';
51              
52 41 100   41 0 196 sub is_array_ref { ref $_[0] && reftype $_[0] eq 'ARRAY' }
53              
54 18 50   18 0 137 sub is_hash_ref { ref $_[0] && reftype $_[0] eq 'HASH' }
55              
56 2 50   2 0 23 sub is_code_ref { ref $_[0] && reftype $_[0] eq 'CODE' }
57              
58             our @CONFIG = ( {
59             remember_last_used_dbh => 1,
60             return_result_sets_as_ref => 0,
61             fetch_row_using => sub {
62             $_[0]->fetchrow_hashref('NAME_lc')
63             },
64             } );
65              
66             sub throw {
67 0 0   0 0 0 @_ = $@ = ref $_[0] ? ref $_[0] : sprintf @_ ? shift : $@, @_ ;
    0          
68 0 0       0 defined &Carp::croak ? goto &Carp::croak : die;
69             };
70              
71             sub config {
72 0 0 0 0 1 0 shift if @_ && __PACKAGE__ eq "$_[0]";
73            
74 0 0       0 if (@_) {
75 0 0       0 if (is_hash_ref($_[0])) {
76 0         0 $CONFIG[-1] = shift;
77             }
78             else {
79 0         0 my %config = @_;
80            
81 0         0 while (my ($key, $value) = each %config) {
82 0         0 $CONFIG[-1]{$key} = $value;
83              
84 0 0 0     0 if ($key eq 'dbh' && !exists($config{remember_last_used_dbh})) {
85 0 0       0 $config{remember_last_used_dbh} = $CONFIG[-1]{$key} ? 0 : 1;
86             }
87             }
88             }
89             }
90              
91 0         0 return $CONFIG[-1];
92             }
93              
94             sub push_config {
95 0 0 0 0 1 0 shift if @_ && __PACKAGE__ eq "$_[0]";
96 0         0 push @CONFIG, { %{ $CONFIG[-1] } };
  0         0  
97 0 0       0 @_ ? &config : $CONFIG[-1];
98             }
99              
100             sub pop_config {
101 0 0 0 0 1 0 shift if @_ && __PACKAGE__ eq "$_[0]";
102 0 0       0 pop @CONFIG if @CONFIG > 1;
103 0 0       0 @_ ? &config : $CONFIG[-1];
104             }
105              
106             # Determine number and style of placeholders used in the SQL statement. A
107             # hash-reference containing "style" and "count" elements is returned. If
108             # no placeholders were found then undef is returned.
109              
110             sub placeholder_disposition {
111 16 50 33 16 0 4444 shift if @_ && __PACKAGE__ eq "$_[0]";
112            
113 16 50       31 my $sql = shift
114             or return;
115            
116 16         16 my $count = 0;
117 16         18 my $style = undef;
118            
119 16         68 $count += 1 while $sql =~ m{:\d+\b}gso;
120            
121 16 100       29 if ($count) {
122 5         6 $style = ':1';
123             }
124             else {
125 11         62 $count += 1 while $sql =~ m{:\w+\b}gso;
126            
127 11 100       23 if ($count) {
128 8         10 $style = ':name';
129             }
130             else {
131 3         16 $count += 1 while $sql =~ m{\?}gso;
132 3 50       7 $style = '?' if $count;
133             }
134             }
135              
136 16 100       63 wantarray ? ( style => $style, count => $count ) : $style;
137             }
138              
139             # If the statement contains :1-style or :name-style placeholders then they
140             # will be converted to the standard ?-style placeholders and parameters
141             # are ordered accordingly.
142              
143             sub normalize {
144 10 50 33 10 0 6428 shift if @_ && __PACKAGE__ eq "$_[0]";
145            
146 10 50       20 my $sql = shift
147             or return;
148              
149 10 50       18 my $style = placeholder_disposition($sql)
150             or return $sql;
151            
152 10         13 my $params = do {
153 10         12 my $argc = @_;
154            
155 10 100       22 if ($style eq ':name') {
    100          
156 6 100 100     29 if ($argc == 1 && is_hash_ref($_[0])) {
    100 66        
157 2         3 +{ %{ $_[0] } };
  2         10  
158             }
159             elsif ($argc == 1 && is_array_ref($_[0])) {
160 2         3 +{ @{ $_[0] } };
  2         10  
161             }
162             else {
163 2         10 +{ @_ };
164             }
165             }
166             elsif ($style eq ':1') {
167 3         3 my $position = 0;
168            
169 3 100 100     18 if ($argc == 1 && is_hash_ref($_[0])) {
    100 66        
170 1         2 +{ %{ $_[0] } };
  1         3  
171             }
172             elsif ($argc == 1 && is_array_ref($_[0])) {
173 1         2 +{ map { ( ':' . ++$position => $_ ) } @{ $_[0] } };
  2         5  
  1         3  
174             }
175             else {
176 1         3 +{ map { ( ':' . ++$position => $_ ) } @_ };
  2         10  
177             }
178             }
179             else {
180 1 50 33     6 if ($argc == 1 && is_array_ref($_[0])) {
181 0         0 [ @{ $_[0] } ];
  0         0  
182             }
183             else {
184 1         3 [ @_ ];
185             }
186             }
187             };
188              
189 10 100       50 if (is_hash_ref($params)) {
190 9         11 for my $k (keys %{$params}) {
  9         24  
191 18 100       48 unless (substr($k, 0, 1) eq ':') {
192 6         17 $params->{':' . $k} = delete $params->{$k};
193             }
194             }
195              
196 9         16 my @ph_names;
197              
198 9         48 while ($sql =~ m{(:\w+)\b}gso) {
199 18         66 push @ph_names, $1;
200             }
201              
202 9         13 for my $name (@ph_names) {
203 18         30 my $value = $params->{$name};
204              
205 18 50       28 if (is_array_ref($value)) {
206 0 0 0     0 unless ($#{$value} == 1 && is_hash_ref($value->[1])) {
  0         0  
207 0         0 my $replacement = join ', ', map { '?' } @{$value};
  0         0  
  0         0  
208 0         0 s{$name\b}{$replacement}gs for $sql;
209 0         0 next;
210             }
211             }
212              
213 18         303 s{$name\b}{?}gs for $sql;
214             }
215              
216 18         28 $params = [ map {
217 9         14 my $value = $params->{$_};
218 18 50       29 if (is_array_ref($value)) {
219 0 0 0     0 $#{$value} == 1 && is_hash_ref($value->[1]) ? $value : @{$value};
  0         0  
220             }
221             else {
222 18         47 $value;
223             }
224             } @ph_names ];
225             }
226              
227 10         21 return $sql, @{$params};
  10         47  
228             }
229              
230             # Prepare (if necessary), bind parameters to and execute the SQL statement
231             # applying any optional callbacks to the result. The result is returned as
232             # reference.
233              
234             sub process {
235 0 0 0 0 1   shift if @_ && __PACKAGE__ eq "$_[0]";
236            
237 0           my ($callbacks, @args) = &callbacks;
238              
239 0           my $config = $CONFIG[-1];
240              
241 0           my $dbh = do {
242 0 0         if (is_code_ref($args[0])) {
243 0           $args[0] = $args[0]->();
244             }
245              
246 0 0         if (my $class = blessed $args[0]) {
247 0 0         if ($class->can('prepare')) {
248 0           shift @args;
249             }
250             }
251             };
252              
253 0 0         if ($dbh) {
254 0 0         $config->{dbh} = $dbh if $config->{remember_last_used_dbh};
255             }
256             else {
257 0           $dbh = $config->{dbh};
258             }
259              
260 0 0         throw $E_NO_DBH unless $dbh;
261              
262 0           my $sth = do {
263 0 0         if (is_code_ref($args[0])) {
264 0           $args[0] = $args[0]->();
265             }
266            
267 0 0         if (my $class = blessed $args[0]) {
268 0 0         if ($class->can('execute')) {
269 0           shift @args;
270             }
271             else {
272 0           throw $E_EXP_STH, $class;
273             }
274             }
275             else {
276 0           undef;
277             }
278             };
279              
280 0 0         if ($sth) { # Statement was already prepared...
281 0 0         if ($sth->{NUM_OF_PARAMS}) {
282 0           my $sql = $sth->{Statement};
283 0           my $style = placeholder_disposition($sql);
284              
285 0 0         if ($style eq ':name') {
    0          
286 0           my %params = @args;
287            
288 0           while ($sql =~ m{:(\w+)\b}gso) {
289 0           my $name = $1;
290 0 0         my $value = exists $params{$name}
    0          
291             ? $params{$name} : exists $params{":$name"}
292             ? $params{":$name"} : undef;
293 0 0         if (is_array_ref($value)) {
294 0 0 0       if ($#{$value} == 1 && is_hash_ref($value->[1])) {
  0            
295 0           $sth->bind_param(":$name", @{$value});
  0            
296             }
297             }
298             else {
299 0           $sth->bind_param(":$name", $value);
300             }
301             }
302             }
303             elsif ($style eq ':1') {
304 0           for my $position (1 .. @args) {
305 0           my $arg = $args[ $position - 1 ];
306 0 0         if (is_array_ref($arg)) {
307 0 0 0       if ($#{$arg} == 1 && is_hash_ref($arg->[1])) {
  0            
308 0           $sth->bind_param(":$position", @{$arg});
  0            
309             }
310             }
311             else {
312 0           $sth->bind_param(":$position", $arg);
313             }
314             }
315             }
316             else {
317 0           for my $position (1 .. @args) {
318 0           my $arg = $args[ $position - 1 ];
319 0 0         if (is_array_ref($arg)) {
320 0 0 0       if ($#{$arg} == 1 && is_hash_ref($arg->[1])) {
  0            
321 0           $sth->bind_param($position, @{$arg});
  0            
322             }
323             }
324             else {
325 0           $sth->bind_param($position, $arg);
326             }
327             }
328             }
329             }
330             }
331             else { # Statement was not prepared...
332 0           my ($sql, @params) = normalize(@args);
333              
334 0 0         throw $E_NO_SQL unless $sql;
335              
336 0           eval { $sth = $dbh->prepare($sql) };
  0            
337              
338 0 0         throw if $@;
339              
340 0 0         if ($sth->{NUM_OF_PARAMS}) {
341 0           for my $position (1 .. @params) {
342 0           my $arg = $params[ $position - 1 ];
343 0 0         if (is_array_ref($arg)) {
344 0 0 0       if ($#{$arg} == 1 && is_hash_ref($arg->[1])) {
  0            
345 0           $sth->bind_param($position, @{$arg});
  0            
346             }
347             }
348             else {
349 0           $sth->bind_param($position, $arg);
350             }
351             }
352             }
353             }
354              
355 0           eval { $sth->execute };
  0            
356              
357 0 0         throw if $@;
358              
359 0           my @results;
360              
361 0 0         if ($sth->{NUM_OF_FIELDS}) {
362 0           while (my $row = $config->{fetch_row_using}->($sth)) {
363 0           push @results, $callbacks->yield($row);
364             }
365            
366 0           $sth->finish;
367              
368 0 0         if ($config->{return_result_sets_as_ref}) {
369 0 0         if ($config->{auto_pop_config}) {
370 0 0         @CONFIG > 1
371             ? pop_config()
372             : delete $config->{auto_pop_config};
373             }
374              
375 0           return \@results;
376             }
377             }
378             else {
379 0           @results = $callbacks->yield($sth->rows);
380            
381 0           $sth->finish;
382              
383 0 0         if ($config->{auto_pop_config}) {
384 0 0         @CONFIG > 1
385             ? pop_config()
386             : delete $config->{auto_pop_config};
387             }
388             }
389              
390             return wantarray
391             ? @results
392 0 0         : @results != 1 ? @results : $results[0];
    0          
393             }
394              
395             1;
396              
397             __END__