File Coverage

blib/lib/EZDBI.pm
Criterion Covered Total %
statement 21 156 13.4
branch 0 90 0.0
condition 1 20 5.0
subroutine 6 18 33.3
pod 8 8 100.0
total 36 292 12.3


line stmt bran cond sub pod time code
1             package EZDBI;
2 3     3   12378 use DBI;
  3         73379  
  3         200  
3 3     3   33 use strict;
  3         6  
  3         82  
4 3     3   15 use Carp;
  3         10  
  3         276  
5 3     3   18 use vars ('$E', '@EXPORT', '$VERSION', '$MAX_STH');
  3         6  
  3         411  
6             require 5;
7              
8             my $DBH;
9             *E = \$DBI::errstr;
10             my $sth_cache; # string to statement handle cache
11             my $sth_cacheA; # oldest first (LRU) handle order
12             $VERSION = 0.13;
13              
14             # Note that this package does NOT inherit from Exporter
15             @EXPORT = qw(Connect Delete Disconnect Insert Select Sql Update Use);
16             sub import {
17 3     3   15 no strict 'refs';
  3         5  
  3         7309  
18 3     3   29 my ($package, %parms) = @_;
19 3         8 my $caller = caller;
20              
21             #This is per database handle
22 3   50     28 $MAX_STH = $parms{maxQuery} || 10;
23              
24 3         8 for my $func (@EXPORT) {
25 24         41 *{"$caller\::$func"} = \&$func;
  24         2036  
26             }
27             }
28              
29             sub Connect {
30 0     0 1   my ($type, @args) = @_;
31 0 0         unless( $type ){
32 0 0         defined($DBH) ? return $DBH : croak "Not connected to a database";
33             }
34              
35 0 0         if( ref($type) eq 'HASH' ){
36 0   0       my $cfg = _parseIni(-file=>
37             $type->{ini}||
38             $ENV{'DBIX_CONN'}||
39             $ENV{HOME}.'/.appconfig-dbi',
40             -label=>$type->{label});
41 0           @args = (
42             $cfg->{user},
43             $cfg->{pass},
44 0 0         $type->{attr} ? {%{$cfg->{attr}}, %{$type->{attr}}} : $cfg->{attr}
  0            
45             );
46 0           $cfg->{dsn} =~ s/^dbi://i;
47 0 0         if( $cfg->{dsn} =~ /\?$/ ){
48 0 0         croak("Section '$type->{label}' requires a database name") unless
49             exists($type->{database});
50 0           $cfg->{dsn} =~ s/\?$/$type->{database}/;
51             }
52 0           $type = $cfg->{dsn};
53             }
54 0 0 0       if ($type =~ /^Pg:(.*)/ && $1 !~ /dbname=/) {
55 0           $type = "Pg:dbname=$1";
56             }
57 0 0         unless ($DBH = DBI->connect("DBI:$type", @args)) {
58 0           croak "Couldn't connect to database: $E";
59             }
60 0           $sth_cacheA->{$DBH} = [];
61 0           return $DBH;
62             }
63              
64             sub Delete {
65 0     0 1   my ($str, @args) = @_;
66 0           my $sth = _substitute('Delete', $str, scalar @args);
67 0           my $rc;
68 0 0         unless ($rc = $sth->execute(@args)) {
69 0           croak "Delete failed: $E";
70             }
71 0           $sth->finish();
72 0           $rc;
73             }
74              
75             sub Disconnect {
76 0 0   0 1   defined($DBH) || croak "Not connected to a database";
77 0   0       my $dbh = $_[0] || $DBH;
78 0           delete($_->{$dbh}) for ($sth_cache, $sth_cacheA);
79 0           $DBH->disconnect();
80 0           undef($_[0]);
81 0           undef($DBH);
82             }
83              
84             sub Insert {
85 0     0 1   my ($str, @args) = @_;
86              
87 0 0         if( ref($args[0]) eq 'HASH' ){
88 0           my %hash = %{shift @args};
  0            
89 0           my @cols = sort keys %hash;
90 0 0         $str .= sprintf('(%s) Values(??L) %s',
91             join(', ', @cols), defined($args[1]) ? $args[1] : '');
92 0           @args = @hash{@cols};
93             }
94              
95 0           my $sth = _substitute('Insert', $str, scalar @args);
96 0           my $rc;
97 0 0         unless ($rc = $sth->execute(@args)) {
98 0           croak "Insert failed: $E";
99             }
100 0           $sth->finish();
101 0           $rc;
102             }
103              
104             # select '* from TABLE WHERE...'
105             # Single column: returns list of scalar in list context
106             # Multi column: returns list of arrayrefs in list context
107             # returns closure/object in scalar context
108             # closure/object returns indvidual records as arrayref or hashref
109             sub Select {
110 0     0 1   my ($str, @args) = @_;
111 0           my ($columns) = ($str =~ /^\s*(.*\S+)\s+from\s+/i);
112              
113 0 0         croak "Select in void context" unless defined wantarray;
114              
115 0           my $sth = _substitute('Select', $str, scalar @args);
116 0 0         unless ($sth->execute(@args)) {
117 0           croak "Select failed: $E";
118             }
119              
120 0           my $r;
121 0 0         if( wantarray ){
122 0           $r = $sth->fetchall_arrayref;
123             #XXX * on a single column Table? check length of first row?
124 0 0 0       unless( $columns =~ /^\*/ || $columns =~ /,/ ){
125 0           $_ = $_->[0] foreach @{$r};
  0            
126             }
127 0           $sth->finish();
128 0           return @$r;
129             }
130 0           my $finish;
131             $r = sub {
132 0     0     $_ = ref($_[0]);
133 0 0         my $res =
    0          
    0          
134             /HASH/ ? $sth->fetchrow_hashref :
135             /ARRAY/ ? $sth->fetchrow_arrayref :
136             /SCALAR/ ? 0 :
137             croak qq(Select doesn't understand "$_[0]");
138 0 0 0       unless( $res || $finish){
139 0           $sth->finish();
140 0           $finish = 1;
141 0           return 0;
142             }
143 0           };
144             #XXX This object cannot be inherited
145 0           bless $r, 'EZDBI::Select';
146             }
147             sub EZDBI::Select::DESTROY{
148 0     0     $_[0]->(\"_");
149             }
150              
151             # Freeform execution
152             sub Sql {
153 0 0   0 1   defined($DBH) || croak "Not connected to a database";
154 0           my $caller = caller;
155 0 0         unless ($DBH->do(@_)) {
156 0           croak "Sql failed: $E";
157             }
158             }
159              
160             sub Update {
161 0     0 1   my ($str, @args) = @_;
162              
163 0 0         if( ref($args[0]) eq 'HASH' ){
164 0           my %hash = %{shift @args};
  0            
165 0           my @cols = sort keys %hash;
166 0 0         unless($str =~ /\bset\b\s*$/i){
167 0           $str .= ' Set'
168             }
169 0 0         $str .= ' ' . join(', ', map{"$_=?"}@cols) .
  0            
170             (defined($args[1]) ? shift @args : '');
171 0           @args = (@hash{@cols}, @args);
172             }
173              
174 0           my $sth = _substitute('Update', $str, scalar @args);
175 0           my $rc;
176 0 0         unless ($rc = $sth->execute(@args)) {
177 0           croak "Update failed: $E";
178             }
179 0           $sth->finish();
180 0           $rc;
181             }
182              
183             #Multiple databases, whee!
184             sub Use{
185 0 0   0 1   ref($_[0]) eq 'DBI::db' ? $DBH = $_[0] : croak("Not a DBI handle: $_[0]");
186             }
187              
188             #Private Methods
189             sub _parseIni{
190 0     0     my %parm = @_;
191 0           my $self;
192 0 0         open(my $INI, $parm{'-file'}) || croak("$!: $parm{-file}\n");
193 0           while( <$INI> ){
194 0 0         next if /^\s*$|(?:[\#\;])/;
195 0 0 0       if( /^\s*\[$parm{'-label'}\]/ ..
196             (/^\s*\[(?!$parm{'-label'})/ || eof($INI) ) ){
197 0           /^\s*([^=]+?)\s*=\s*(.*)$/;
198 0 0         $self->{$1} = $2 if $1;
199             }
200             }
201             #Handle DBIx::Connect attr construct
202 0           foreach my $key ( grep {/^attr/} keys %{$self} ){
  0            
  0            
203 0           my $attr = $key;
204 0           $attr =~ s/^attr\s+//i;
205 0           $self->{attr}->{$attr} = delete($self->{$key});
206             }
207              
208 0           croak("Section [$parm{'-label'}] does not exist in $parm{'-file'}") unless
209 0 0         keys %{$self};
210 0           return $self;
211             }
212              
213             # given a query string,
214             sub _substitute {
215 0 0   0     defined($DBH) || croak "Not connected to a database";
216 0           my($function, $str, $args) = @_;
217              
218 0 0         if( $function eq 'Insert' ){
219 0           my $list = join ',' , (('?') x $args);
220 0 0         unless( $str =~ s/\?\?L|\(\s*\?\?L\s*\)/($list)/ ){
221 0 0         if( $str =~ /\bvalues\b/i ){
    0          
222 0 0         $str .= "($list)" unless $str =~ /\)\s*$/;
223             }
224             elsif( $args ){
225 0           $str .= " values ($list)";
226             }
227             }
228             }
229              
230 0           my $subct = $str =~ tr/?/?/;
231 0 0         if( $subct > $args ){
    0          
232 0           croak "Not enough arguments for $function ($subct required)";
233             }
234             elsif( $subct < $args ){
235 0           croak "Too many arguments for $function ($subct required)";
236             }
237              
238 0           my $sth;
239             # was the statement handle cached already?
240 0 0         if( $sth = $sth_cache->{$DBH}->{$str} ){
241             # remove it from the MRU queue (if it is there) and add it to the end
242 0 0         unless( $sth_cacheA->{$DBH}->[-1] eq $str ){
243 0           $sth_cacheA->{$DBH} = [grep($_ ne $str, @{$sth_cacheA->{$DBH}}), $str];
  0            
244             }
245             }
246             else{
247             # expire old cache items if cache is full
248 0 0         if( scalar @{$sth_cacheA->{$DBH}} >= $MAX_STH -1 ){
  0            
249 0           delete(@{$sth_cache->{$DBH}}{splice(@{$sth_cacheA->{$DBH}},0,$MAX_STH/3)});
  0            
  0            
250             }
251            
252             # prepare new handle
253 0           $sth = $DBH->prepare("$function $str");
254 0 0         croak "Couldn't prepare query for '$function $str': $E; aborting" unless $sth;
255            
256             # install new handle in cache
257 0           $sth_cache->{$DBH}->{$str} = $sth;
258 0           push(@{$sth_cacheA->{$DBH}}, $str);
  0            
259             }
260 0           return $sth;
261             }
262              
263             1;
264             __END__