File Coverage

blib/lib/DBIx/LazyMethod.pm
Criterion Covered Total %
statement 70 231 30.3
branch 5 112 4.4
condition 1 28 3.5
subroutine 20 25 80.0
pod 2 2 100.0
total 98 398 24.6


line stmt bran cond sub pod time code
1             package DBIx::LazyMethod;
2              
3             #DBIx::LazyMethod for the lazy hest $Id: LazyMethod.pm,v 1.3 2004/03/27 13:45:58 cwg Exp $
4             #Lazy DBI encapsulation for simple DB handling
5              
6 4     4   168687 use 5.005;
  4         15  
  4         154  
7 4     4   24 use strict;
  4         6  
  4         182  
8 4     4   22 use Carp;
  4         11  
  4         337  
9 4     4   2548 use DBI;
  4         24076  
  4         179  
10 4     4   26 use Exporter;
  4         8  
  4         141  
11 4     4   24 use vars qw($VERSION $AUTOLOAD @EXPORT @ISA);
  4         7  
  4         417  
12              
13 4     4   21 use constant RETURN_VALUES => qw(WANT_ARRAY WANT_ARRAYREF WANT_HASHREF WANT_ARRAY_HASHREF WANT_RETURN_VALUE WANT_AUTO_INCREMENT); #The return value names
  4         6  
  4         658  
14             @EXPORT = RETURN_VALUES;
15             @ISA = qw(Exporter);
16             $VERSION = do { my @r=(q$Revision: 1.3 $=~/\d+/g); sprintf "%d."."%02d"x$#r,@r };
17             my $PACKAGE = "[DBIx::LazyMethod]";
18              
19             #Public exported constants
20 4     4   22 use constant WANT_ARRAY => 1;
  4         7  
  4         180  
21 4     4   18 use constant WANT_ARRAYREF => 2;
  4         6  
  4         170  
22 4     4   60 use constant WANT_HASHREF => 3;
  4         14  
  4         162  
23 4     4   18 use constant WANT_ARRAY_HASHREF => 4;
  4         7  
  4         166  
24 4     4   17 use constant WANT_RETURN_VALUE => 5;
  4         8  
  4         158  
25 4     4   25 use constant WANT_AUTO_INCREMENT => 6;
  4         7  
  4         324  
26 4     4   23 use constant WANT_METHODS => (WANT_ARRAY,WANT_ARRAYREF,WANT_HASHREF,WANT_ARRAY_HASHREF,WANT_RETURN_VALUE,WANT_AUTO_INCREMENT); #The return values
  4         8  
  4         210  
27              
28             #Private constants
29 4     4   19 use constant TRUE => 1;
  4         6  
  4         151  
30 4     4   17 use constant FALSE => 0;
  4         8  
  4         194  
31 4     4   26 use constant PRIVATE_METHODS => qw(new AUTOLOAD DESTROY _connect _disconnect _error);
  4         5  
  4         187  
32              
33             #debug constant
34 4     4   18 use constant DEBUG => 0;
  4         7  
  4         11067  
35              
36             #methods
37             sub new {
38 1     1 1 1466 my $class = shift;
39 1         7 my %args = @_;
40 1   33     10 my $self = bless {}, ref $class || $class;
41              
42             #did we get methods?
43 1         3 my $methods_ref = $args{'methods'};
44 1 50       5 unless (ref $methods_ref eq 'HASH') {
45 0         0 die "$PACKAGE invalid methods definition: argument methods must be hashref";
46             }
47             #anything in it?
48 1 50       8 unless (keys %$methods_ref > 0) {
49 0         0 die "$PACKAGE no methods in methods hash";
50             }
51             #lets check the stuff
52 1         8 my ($dbd_name) = $args{'data_source'} =~ /^dbi:(.*?):/i;
53             #this approach will have to change when we start to accept an already create DBI handle
54 1         3 my $good_methods = 0;
55 1         5 foreach my $meth (keys %$methods_ref) {
56             #check for internal names / reserwed words in method names
57 1 50       3 if (grep { $meth eq $_ } PRIVATE_METHODS) {
  6         19  
58 0         0 die "$PACKAGE method name $meth is a reserved method name";
59             }
60             #A way to validate SQL could be nice.
61 1 0       2 unless (defined ${%$methods_ref}{$meth}->{sql}) {
  1         124  
62 0         0 die "$PACKAGE method $meth: missing SQL";
63             }
64 0 0       0 unless (defined ${%$methods_ref}{$meth}->{args}) {
  0         0  
65 0         0 die "$PACKAGE method $meth: missing argument definition";
66             }
67 0 0       0 unless (defined ${%$methods_ref}{$meth}->{ret}) {
  0         0  
68 0         0 die "$PACKAGE method $meth: missing return data definition";
69             }
70 0 0       0 unless (ref ${%$methods_ref}{$meth}->{args} eq 'ARRAY') {
  0         0  
71 0         0 die "$PACKAGE method $meth: bad argument list";
72             }
73              
74             #check if we got the right amout of args - Cleanup on isle 9!
75 0         0 my $arg_count = @{${%$methods_ref}{$meth}->{args}};
  0         0  
  0         0  
76             #we should probably rather get amount of placeholders from DBI at some point. But then we can't do it before a prepare.
77 0         0 my @placeholders = ${%$methods_ref}{$meth}->{sql} =~ m/\?/g;
  0         0  
78              
79 0 0       0 unless ($arg_count == scalar @placeholders) {
80 0         0 warn "$PACKAGE method $meth: argument list does not match number of placeholders in SQL. You should get an error from DBI.";
81             }
82              
83             #check DBD specific issues
84 0 0       0 if (${%$methods_ref}{$meth}->{ret} eq WANT_AUTO_INCREMENT) {
  0         0  
85 0 0       0 unless (grep { lc $dbd_name eq $_ } qw(mysql pg)) {
  0         0  
86 0         0 die "$PACKAGE return value type WANT_AUTO_INCREMENT not supported by $dbd_name DBD in method $meth";
87             }
88             }
89              
90 0 0       0 unless (grep { ${%$methods_ref}{$meth}->{ret} eq $_ } WANT_METHODS ) {
  0         0  
  0         0  
91 0         0 die "$PACKAGE bad return value definition in method $meth";
92             }
93              
94             # Since 'noprepare' causes us to do a $dbh->do, we cannot return anything else than WANT_RETURN_VALUE
95 0 0 0     0 if ((${%$methods_ref}{$meth}->{ret} != WANT_RETURN_VALUE) && (defined ${%$methods_ref}{$meth}->{'noprepare'})) {
  0         0  
  0         0  
96 0         0 die "$PACKAGE return value for $meth must be WANT_RETURN_VALUE if 'noprepare' option is used";
97             }
98              
99             # Use of 'noquote' option is depending on 'noprepare' option. Check that it is set.
100 0 0 0     0 if (defined (${%$methods_ref}{$meth}->{'noquote'}) && (!defined ${%$methods_ref}{$meth}->{'noprepare'})) {
  0         0  
  0         0  
101 0         0 warn "$PACKAGE useless use of 'noquote' option without required 'noprepare' option for method $meth";
102             }
103              
104 0         0 $good_methods++;
105             }
106 0 0       0 unless ($good_methods > 0) {
107 0         0 die "$PACKAGE no usable methods in methods hashref";
108             }
109              
110             #TODO: more input checking?
111             #At some point an existing $dbh object could be passed as an argument to new() instead of this.
112 0         0 $self->{'methods'} = $methods_ref;
113 0   0     0 $self->{'_data_source'} = $args{'data_source'} || die "Argument data_source missing";
114 0   0     0 $self->{'_user'} = $args{'user'} || "";
115 0   0     0 $self->{'_pass'} = $args{'pass'} || undef;
116 0   0     0 $self->{'_attr'} = $args{'attr'} || undef;
117             #connect us
118 0         0 $self->{'_dbh'} = $self->_connect;
119            
120 0         0 return $self;
121             }
122              
123             sub AUTOLOAD {
124 0     0   0 my $self = shift;
125 0         0 my %args = @_;
126 0         0 my ($meth) = $AUTOLOAD =~ /.*::([\w_]+)/;
127              
128             #clear the error register
129 0         0 $self->_error(FALSE);
130              
131             #is it a DBI statement handle
132 0 0       0 if ($AUTOLOAD =~ /.*::_sth_([\w_]+)/) {
    0          
133              
134             #unless it is already created
135 0 0       0 return if defined $self->{'_sth_'.$1};
136              
137             #we need a DBI handle
138             #exists $self->{_dbh} or return $self->_error("DBI handle missing");
139 0 0 0     0 unless (exists $self->{_dbh} && ref $self->{_dbh} eq 'DBI::db') { return $self->_error("DBI handle missing"); }
  0         0  
140              
141             #and a matching method
142 0 0       0 exists $self->{'methods'}{$1} or $self->_error("Method ".$1." not defined");
143              
144             #check special method and dbd bindings
145             #unless (($self->{'methods'}{$1} eq 'mysql') && ($self->{_dbh}->{Driver}->{Name} eq 'mysql')) {
146             # die "You cannot use exists $self->{'methods'}{$1} or $self->_error("Method ".$1." not defined");
147              
148             #we create a new DBI statement handle - unless it's a no-prepare type
149 0 0       0 if (defined $self->{'methods'}{$1}->{'noprepare'}) {
150 0         0 $self->{'_sth_'.$1} = TRUE; #faking it
151             } else {
152 0         0 print STDERR "$PACKAGE DEBUG: preparing ".$self->{'methods'}{$1}->{sql}."\n" if DEBUG;
153 0 0       0 $self->{'_sth_'.$1} = $self->{_dbh}->prepare($self->{'methods'}{$1}->{sql}) or return $self->_error($meth." prepare failed");
154             }
155              
156             # Use this DBI built-in some day
157             # $self->{'_sth_'.$1}->{'NUM_OF_FIELDS'}
158 0         0 return;
159             }
160             #is it a method
161             elsif (defined $self->{'methods'}{$meth}) {
162            
163             #call the associated DBI statement handle (which is then automagically created)
164 0         0 my $sthname = "_sth_".$meth;
165 0         0 $self->$sthname();
166            
167             #and it the statement handle will appear on the self object
168 0         0 my $sth = $self->{"_sth_".$meth};
169              
170 0         0 my ($argsref) = $self->{'methods'}{$meth}->{args};
171             #put the required bind values here
172 0         0 my @bind_values = ();
173 0         0 my $cnt = 1;
174             #run through the args defined for the method
175 0         0 foreach (@$argsref) {
176 0 0       0 unless (defined $args{$_}) {
177 0         0 return $self->_error($meth." Insufficient parameters (".$_.")");
178             }
179             #the argument was provided, so we use it
180 0         0 push @bind_values, $args{$_};
181             #for checking argument count later
182 0         0 delete $args{$_};
183              
184             #puha hack for placeholders til MySQL limit syntax
185             #TODO: investigate how this can be done in Pg
186 0 0       0 next unless ($self->{_dbh}->{Driver}->{Name} eq 'mysql');
187              
188             # If we haven't prepared the $sth, then don't call it
189 0 0       0 next unless (defined $self->{'methods'}{$meth}->{'noprepare'});
190              
191 0 0       0 if ($_ =~ /^limit_/) { $self->{"_sth_".$meth}->bind_param($cnt,'',DBI::SQL_INTEGER); }
  0         0  
192 0         0 $cnt++;
193             }
194              
195             #warn if more arguments than needed was provided
196 0         0 foreach (keys %args) {
197 0         0 warn "$PACKAGE WARN: useless argument \"".$_."\" provided for method \"".$meth."\"";
198             }
199              
200             #do it
201 0         0 my $rv;
202 0 0       0 if (defined $self->{'methods'}{$meth}->{'noprepare'}) {
203             # Execute the SQL directly - as we have no prepared $sth
204 0         0 my $sql = $self->{'methods'}{$meth}->{sql};
205 0 0       0 if (defined $self->{'methods'}{$meth}->{'noquote'}) {
206             # HACK: danger will robinson. danger.
207 0         0 my $sql = $self->{'methods'}{$meth}->{sql};
208 0         0 $sql =~ s/\?+?/(shift @bind_values)/oe while (@bind_values);
  0         0  
209 0 0       0 $rv = $self->{_dbh}->do($sql) or return $self->_error("_sth_".$meth." do failed : ".DBI::errstr);
210             } else {
211             # Let's quote the bind_values
212             #$sql =~ s/\?+?/($self->{_dbh}->quote_identifier(shift @bind_values))/oe while (@bind_values);
213 0 0       0 $rv = $self->{_dbh}->do($self->{'methods'}{$meth}->{sql},undef,@bind_values) or return $self->_error("_sth_".$meth." do failed : ".DBI::errstr);
214             }
215             } else {
216             # Execute the query normally on the statement handle
217 0 0       0 $rv = $sth->execute(@bind_values) or return $self->_error("_sth_".$meth." execute failed : ".DBI::errstr);
218             }
219 0 0 0     0 print STDERR "$PACKAGE DEBUG: $meth DBI: ".DBI::errstr."\n" if (!$rv && DEBUG);
220 0 0       0 unless ($rv) { return $self->_error("DBI execute error: ".DBI::errstr); }
  0         0  
221              
222 0         0 my ($ret) = $self->{'methods'}{$meth}->{ret};
223 0         0 print STDERR "Found ret for $meth: $ret\n" if DEBUG;
224              
225 0 0       0 if ($self->{'methods'}{$meth}->{ret} == WANT_ARRAY) {
    0          
    0          
    0          
    0          
    0          
226 0         0 my @ret;
227 0         0 while (my (@ref) = $sth->fetchrow_array) { push @ret,@ref }
  0         0  
228 0         0 return @ret;
229             } elsif ($self->{'methods'}{$meth}->{ret} == WANT_ARRAYREF) {
230 0         0 my $ret = $sth->fetchrow_arrayref;
231 0 0 0     0 if ((!defined $ret) || (ref $ret eq 'ARRAY')) {
232 0         0 return $ret;
233             } else {
234 0         0 return $self->_error("_sth_".$meth." is doing fetching on a non-SELECT statement");
235             }
236             } elsif ($self->{'methods'}{$meth}->{ret} == WANT_HASHREF) {
237 0         0 my $ret = $sth->fetchrow_hashref;
238 0 0 0     0 if ((!defined $ret) || (ref $ret eq 'HASH')) {
239 0         0 return $ret;
240             } else {
241 0         0 return $self->_error("_sth_".$meth." is doing fetching on a non-SELECT statement");
242             }
243             } elsif ($self->{'methods'}{$meth}->{ret} == WANT_ARRAY_HASHREF) {
244 0         0 my @ret;
245 0         0 while (my $ref = $sth->fetchrow_hashref) {
246 0         0 push @ret, $ref;
247             }
248 0         0 return \@ret;
249             } elsif ($self->{'methods'}{$meth}->{ret} == WANT_AUTO_INCREMENT) {
250              
251 0         0 my $cur_dbd = $self->{_dbh}->{Driver}->{Name};
252 0 0       0 unless ($cur_dbd) { return $self->_error("Unknown DBD '".$cur_dbd."'"); }
  0         0  
253              
254             # TODO: check DBD version to make sure it supports the index/auto_increment stuff
255              
256 0 0       0 if (lc $cur_dbd eq 'mysql') {
    0          
257             #MySQL index/auto_increment hack
258 0 0       0 if (defined $sth->{'mysql_insertid'}) {
259 0         0 return $sth->{'mysql_insertid'};
260             } else {
261 0         0 return $self->_error("_sth_".$meth." could not get mysql_insertid from mysql DBD");
262             }
263             }
264             elsif (lc $cur_dbd eq 'pg') {
265             #PostgreSQL index/auto_increment hack
266 0 0       0 if (defined $sth->{'pg_oid_status'}) {
267 0         0 return $sth->{'pg_oid_status'};
268             } else {
269 0         0 return $self->_error("_sth_".$meth." could not get pg_oid_status from Pg DBD");
270             }
271             } else {
272 0         0 return $self->_error("_sth_".$meth." is using DBD specific AUTO_INCREMENT on unsupported DBD");
273             }
274             } elsif ($self->{'methods'}{$meth}->{ret} == WANT_RETURN_VALUE) {
275 0         0 return $rv;
276             } else {
277 0         0 return $self->_error("No such return type for ".$meth);
278             }
279              
280             } else {
281 0         0 return $self->_error("No such method: $AUTOLOAD");
282             }
283             }
284              
285             sub DESTROY ($) {
286 1     1   3 my $self = shift;
287             #do we have any methods?
288 1 50       10 if (defined $self->{'methods'}) {
289             #remember to bury statement handles
290 0         0 foreach (keys %{$self->{'methods'}}) {
  0         0  
291             #ignore if we haven't used a sth
292 0 0       0 next if (defined $self->{'methods'}{$_}->{'noprepare'});
293             #if the sth of a methods is defined it has been used
294 0 0       0 if (defined $self->{'_sth_'.$_}) {
295             #finish the sth
296 0         0 $self->{'_sth_'.$_}->finish;
297 0         0 print STDERR "$PACKAGE DEBUG: meth DESTROY - finished _sth_".$_." handle\n" if DEBUG;
298             }
299             }
300             }
301             #and hang up if we have a connection
302 1 50       122 if (defined $self->{'_dbh'}) { $self->_disconnect(); }
  0            
303             }
304              
305             sub _connect {
306 0     0     my $self = shift;
307              
308 0           my $data_source = $self->{'_data_source'};
309 0           my $user = $self->{'_user'};
310 0           my $auth = $self->{'_pass'};
311 0           my $attr = $self->{'_attr'};
312              
313             #$dbh = DBI->connect($data_source, $username, $auth, \%attr);
314              
315             #TODO: validate args
316 0 0         if (defined $attr) {
317 0 0         unless ((ref $attr) eq 'HASH') { die "argument 'attr' must be hashref"; }
  0            
318             }
319              
320 0           print STDERR "$PACKAGE DEBUG: DBIx::LazyMethod doing: DBI->connect($data_source, $user, $auth, $attr);\n" if DEBUG;
321 0 0         my $dbh = DBI->connect($data_source, $user, $auth, $attr) or return $self->_error("Connection failure [".DBI::errstr."]");
322 0           return $dbh;
323             }
324              
325             sub _disconnect {
326 0     0     my $self = shift;
327 0           my $dbh = $self->{'_dbh'};
328              
329 0 0         unless (defined $dbh) { return TRUE }
  0            
330              
331 0 0         if (!$dbh->disconnect) {
332 0           $self->_error("Disconnect failed [".DBI::errstr."]");
333             } else {
334 0           print STDERR "$PACKAGE DEBUG: Disconnected dbh\n" if DEBUG;
335             }
336 0           return TRUE;
337             }
338              
339             sub _error {
340 0     0     my ($self,$data) = (shift,shift);
341 0 0         if ($data eq FALSE) {
342 0           delete $self->{'errorstate'};
343 0           $self->{'errormessage'} = "[unknown]";
344             } else {
345 0           $self->{'errorstate'} = TRUE;
346 0           $self->{'errormessage'} = $data;
347 0           warn "$PACKAGE ERROR: ".$data;
348             }
349 0           return;
350             }
351              
352             sub is_error ($) {
353 0     0 1   my $self = shift;
354 0 0         return (defined $self->{'errorstate'})?TRUE:FALSE;
355             }
356              
357             1;
358              
359             __END__