File Coverage

blib/lib/DBIx/Interpolate.pm
Criterion Covered Total %
statement 10 12 83.3
branch n/a
condition n/a
subroutine 4 4 100.0
pod n/a
total 14 16 87.5


line stmt bran cond sub pod time code
1             package DBIx::Interpolate;
2              
3 2     2   60545 use strict;
  2         7  
  2         80  
4 2     2   12 use warnings;
  2         6  
  2         54  
5 2     2   19 use Carp;
  2         4  
  2         127  
6 2     2   3833 use DBI;
  0            
  0            
7             use SQL::Interpolate qw(:all);
8             use base qw(Exporter SQL::Interpolate);
9              
10             our $VERSION = '0.32';
11              
12             our @EXPORT;
13             our %EXPORT_TAGS = (all => [qw(
14             attr
15             dbi_interp
16             key_field
17             make_dbi_interp
18             )]);
19             our @EXPORT_OK = @{$EXPORT_TAGS{all}};
20              
21             sub _wrap(&);
22              
23             # internal helper function to filter use parameters
24             sub _filter_params {
25             my ($skip_names, $skip_keys, @parts) = @_;
26             my @out;
27             my %skip_names = map {($_=>1)} @$skip_names;
28             my %skip_keys = map {($_=>1)} @$skip_keys;
29             while (@parts) {
30             if ($skip_names{$parts[0]}) { shift @parts; }
31             elsif ($skip_keys{$parts[0]}) { shift @parts; shift @parts; }
32             else { push @out, shift @parts; }
33             }
34             return @out;
35             }
36              
37             sub import {
38             my @params = @_;
39              
40             # handle local exports
41             my @params2 = _filter_params($SQL::Interpolate::EXPORT_TAGS{all},
42             [SQL::Interpolate::_use_params()], @params);
43             __PACKAGE__->export_to_level(1, @params2);
44              
45             # pass use parameters to wrapped module.
46             # use goto since non-returnable on FILTER => 1.
47             @_ = _filter_params($EXPORT_TAGS{all}, [], @params);
48             push @_, __WRAP => 1;
49             goto &SQL::Interpolate::import; # @_
50             }
51              
52             sub new {
53             my $class = shift;
54             my $dbh;
55             if (UNIVERSAL::isa($_[0], 'DBI:db')) {
56             $dbh = shift;
57             }
58             elsif (ref($_[0]) eq 'ARRAY') {
59             $dbh = DBI->connect(@{shift @_});
60             }
61              
62             my $self = SQL::Interpolate->new(($dbh ? $dbh : ()), @_);
63             bless $self, $class;
64             $self->{stx} = $self->prepare();
65              
66             return $self;
67             }
68              
69             sub connect {
70             my $class = shift;
71             my $self;
72             eval {
73             my $dbh = DBI->connect(@_);
74             return if !$dbh;
75             $self = DBIx::Interpolate->new($dbh); #Q: OK?
76             };
77             if ($@) { croak $@; }
78             return $self;
79             }
80              
81             sub dbh {
82             my $self = shift;
83             return $self->{dbh};
84             }
85              
86             # new in 0.31
87             sub stx {
88             my $self = shift;
89             return $self->{stx};
90             }
91              
92             sub dbi_interp {
93             my $key;
94             my $attr;
95             my @args = grep {
96             my $save = 1;
97             if (ref($_) eq 'SQL::Interpolate::Key') {
98             $key = $_; $save = 0;
99             }
100             elsif (ref($_) eq 'SQL::Interpolate::Attr') {
101             $attr = {%$_}; $save = 0;
102             }
103             $save;
104             } @_;
105             my ($sql, @bind) = sql_interp(@args);
106             my @params = ($sql);
107             push @params, $$key if defined $key;
108             push @params, $attr, @bind;
109             return @params;
110             }
111              
112             sub make_dbi_interp {
113             my (@params) = @_;
114              
115             my $interp = sub {
116             return dbi_interp(@params, @_);
117             };
118             return $interp;
119             }
120              
121             sub key_field {
122             my $key = shift;
123             return bless \$key, "SQL::Interpolate::Key";
124             }
125              
126             sub attr {
127             return bless {@_}, "SQL::Interpolate::Attr";
128             }
129              
130             # based on function in DBI
131             sub _do_selectrow {
132             my ($self, $method, @list) = @_;
133              
134             #my ($method, $dbh, $stmt, $attr, @bind) = @_;
135             #my $sth = $dbh->prepare($stmt, $attr) or return;
136             #_do_execute($sth, @bind) or return;
137             $self->{stx}->execute(@list) or return;
138             my $sth = $self->{stx}->sth();
139             my $row = $sth->$method() and $sth->finish;
140             return $row;
141             }
142              
143             sub prepare {
144             my ($self) = @_;
145             return DBIx::Interpolate::STX->new($self);
146             }
147              
148             sub do {
149             my ($self, @list) = @_;
150             return _wrap {
151             # based on DBI::do
152             # my $sth = $dbh->prepare($sql, $attr) or return undef;
153             # _do_execute(@bind) or return undef;
154              
155             $self->{stx}->execute(@list) or return undef;
156             my $sth = $self->{stx}->sth();
157             my $rows = $sth->rows;
158             return ($rows == 0) ? "0E0" : $rows;
159             };
160             }
161              
162             sub selectrow_array {
163             my ($self, @list) = @_;
164             my $want = wantarray;
165             return _wrap {
166             # based on DBI::selectrow_array
167              
168             my $row = $self->_do_selectrow('fetchrow_arrayref', @list)
169             or return;
170             return $row->[0] unless $want;
171             return @$row;
172             };
173             }
174              
175             sub selectrow_arrayref {
176             my ($self, @list) = @_;
177             return _wrap {
178             # based on DBI::selectrow_arrayref
179              
180             return $self->_do_selectrow('fetchrow_arrayref', @list);
181             };
182             }
183              
184             sub selectrow_hashref {
185             my ($self, @list) = @_;
186             return _wrap {
187             # based on DBI::selectrow_hashref
188              
189             return $self->_do_selectrow('fetchrow_hashref', @list);
190             };
191             }
192              
193             sub selectall_arrayref {
194             my ($self, @list) = @_;
195             return _wrap {
196             # improve: no need to to a full dbi_interp call here and elsewhere
197             my ($sql, $attr, @bind) = $self->dbi_interp(@list); # need $attr
198              
199             # based on DBI::selectall_arrayref
200             # my $sth = $dbh->prepare($sql, $attr) or return;
201             # _do_execute($sth, @bind) or return;
202              
203             $self->{stx}->execute(@list) or return;
204             my $sth = $self->{stx}->sth();
205             # typically undef, else hash or array ref
206             my $slice = $attr->{Slice};
207             if (!$slice and $slice=$attr->{Columns}) {
208             if (ref $slice eq 'ARRAY') {
209             $slice = [ @{$attr->{Columns}} ];
210             for (@$slice) { $_-- }
211             }
212             }
213             my $rows = $sth->fetchall_arrayref(
214             $slice, my $MaxRows = $attr->{MaxRows});
215             $sth->finish if defined $MaxRows;
216             return $rows;
217             };
218             }
219              
220             sub selectall_hashref {
221             my ($self, @list) = @_;
222             return _wrap {
223             #need $key_field
224             my ($sql, $key_field, $attr, @bind) = $self->dbi_interp(@list);
225              
226             # based on DBI::selectall_hashref
227             # my $sth = $dbh->prepare($sql, $attr);
228             # return unless $sth;
229             # _do_execute($sth, @bind) or return;
230              
231             $self->{stx}->execute(@list) or return;
232             my $sth = $self->{stx}->sth();
233             return $sth->fetchall_hashref($key_field);
234             };
235             }
236              
237             sub selectcol_arrayref {
238             my ($self, @list) = @_;
239             return _wrap {
240             my ($sql, $attr, @bind) = $self->dbi_interp(@list); # need $attr
241              
242             # based on DBI::selectcol_arrayref
243             # my $sth = $dbh->prepare($sql, $attr);
244             # return unless $sth;
245             # _do_execute($sth, @bind) or return;
246              
247             $self->{stx}->execute(@list) or return;
248             my @columns = ($attr->{Columns}) ? @{$attr->{Columns}} : (1);
249             my @values = (undef) x @columns;
250             my $idx = 0;
251             my $sth = $self->{stx}->sth();
252             for (@columns) {
253             $sth->bind_col($_, \$values[$idx++]) || return;
254             }
255             my @col;
256             if (my $max = $attr->{MaxRows}) {
257             push @col, @values while @col<$max && $sth->fetch;
258             }
259             else {
260             push @col, @values while $sth->fetch;
261             }
262             return \@col;
263             };
264             }
265              
266             sub _wrap(&) {
267             my $code = shift;
268             my $x;
269             my @x;
270             my $want = wantarray();
271             eval {
272             if ($want) { @x = $code->(); }
273             else { $x = $code->(); }
274             };
275             if ($@) { croak $@; }
276             return $want ? @x : $x;
277             }
278              
279             #old: sub _do_execute {
280             # my ($sth, @bind) = @_;
281             # if (ref($bind[0]) eq 'ARRAY') {
282             # _bind_params($sth, @bind);
283             # return $sth->execute();
284             # }
285             # else {
286             # return $sth->execute(@bind);
287             # }
288             #}
289             #old: sub _bind_params {
290             # my ($sth, @bind) = @_;
291             # my $num = 1;
292             # for my $val (@bind) {
293             # $sth->bind_param($num++, $val->[0], $val->[1]->{type});
294             # }
295             #}
296              
297             1;
298              
299             package DBIx::Interpolate::STX;
300             use strict;
301              
302             sub new {
303             my ($class, $dbx) = @_;
304             my $self = bless {
305             # active sth
306             sth => undef,
307              
308             # map: SQL --> sth (sth cache)
309             sths => {},
310              
311             # queue of SQL. used to select sth to delete if cache is full
312             sql_queue => [],
313              
314             # DBIx::Interpolate
315             dbx => $dbx,
316              
317             # max sths allowed in the cache
318             max_sths => 1
319             }, $class;
320             return $self;
321             }
322              
323             sub max_sths {
324             my ($self, $max_sths) = @_;
325             if (defined $max_sths) {
326             $self->{max_sths} = $max_sths;
327             }
328             else {
329             return $self->{max_sths};
330             }
331             }
332              
333             sub sth {
334             my $self = shift;
335             return $self->{sth};
336             }
337              
338             sub sths {
339             my $self = shift;
340             return {%{$self->{sths}}};
341             }
342              
343             sub execute {
344             my ($self, @list) = @_;
345             my $dbx = $self->{dbx};
346             return DBIx::Interpolate::_wrap {
347             my ($sql, @bind) = $dbx->dbi_interp(@list);
348             shift @bind if defined $bind[0] && ref $bind[0] eq ''; # remove any key_field()
349             my $attr = shift @bind;
350             my $sth = $self->{sths}->{$sql};
351             if (! defined $sth) {
352             my $dbx = $self->{dbx};
353             $sth = $dbx->dbh()->prepare($sql, $attr) or return;
354             if (@{$self->{sql_queue}} + 1 > $self->{max_sths}) {
355             my $sql_remove = shift @{$self->{sql_queue}};
356             delete $self->{sths}->{$sql_remove};
357             }
358             $self->{sths}->{$sql} = $sth;
359             push @{$self->{sql_queue}}, $sql;
360             }
361             $self->{sth} = $sth;
362             _bind_params($sth, @bind);
363             return $sth->execute();
364             };
365             }
366              
367             sub _bind_params {
368             my ($sth, @bind) = @_;
369             my $num = 1;
370             return DBIx::Interpolate::_wrap {
371             if (ref($bind[0]) eq 'ARRAY') {
372             for my $val (@bind) {
373             $sth->bind_param($num++, $val->[0], $val->[1]->{type});
374             }
375             }
376             else {
377             for my $val (@bind) {
378             $sth->bind_param($num++, $val);
379             }
380             }
381             };
382             }
383              
384             sub fetchrow_arrayref {
385             my $self = shift;
386             return DBIx::Interpolate::_wrap {
387             return $self->{sth}->fetchrow_arrayref();
388             };
389             }
390              
391             sub fetchrow_array {
392             my $self = shift;
393             return DBIx::Interpolate::_wrap {
394             return $self->{sth}->fetchrow_array();
395             };
396             }
397              
398             sub fetchrow_hashref {
399             my ($self, @params) = @_;
400             return DBIx::Interpolate::_wrap {
401             return $self->{sth}->fetchrow_hashref(@params);
402             };
403             }
404              
405             sub fetchall_arrayref {
406             my ($self, @params) = @_;
407             return DBIx::Interpolate::_wrap {
408             return $self->{sth}->fetchall_arrayref(@params);
409             };
410             }
411              
412             sub fetchall_hashref {
413             my ($self, @params) = @_;
414             return DBIx::Interpolate::_wrap {
415             return $self->{sth}->fetchall_hashref(@params);
416             };
417             }
418              
419             1;
420              
421             __END__