File Coverage

blib/lib/CPAN/Testers/Common/DBUtils.pm
Criterion Covered Total %
statement 123 171 71.9
branch 44 98 44.9
condition 13 35 37.1
subroutine 19 22 86.3
pod 10 10 100.0
total 209 336 62.2


line stmt bran cond sub pod time code
1             package CPAN::Testers::Common::DBUtils;
2              
3 6     6   269389 use warnings;
  6         16  
  6         2078  
4 6     6   41 use strict;
  6         11  
  6         239  
5              
6 6     6   35 use vars qw($VERSION);
  6         17  
  6         503  
7             $VERSION = '0.09';
8              
9             =head1 NAME
10              
11             CPAN::Testers::Common::DBUtils - Basic Database Wrapper
12              
13             =head1 SYNOPSIS
14              
15             use CPAN::Testers::Common::DBUtils;
16              
17             my $dbx = CPAN::Testers::Common::DBUtils->new(
18             driver => 'mysql',
19             database => 'testdb');
20              
21             sub errors { print STDERR "Error: $_[0], sql=$_[1]\n" }
22             my $dbi = CPAN::Testers::Common::DBUtils->new(
23             driver => 'CSV',
24             dbfile => '/var/www/mysite/db
25             errsub => \&errors);
26              
27             my @arr = $dbi->get_query('array',$sql);
28             my @arr = $dbi->get_query('array',$sql,$id);
29             my @arr = $dbi->get_query('hash', $sql,$id);
30              
31             my $id = $dbi->id_query($sql,$id,$name);
32             $dbi->do_query($sql,$id);
33              
34             $dbi->do_commit(); # where AutoCommit is disabled
35              
36             # array iterator
37             my $next = $dbi->iterator('array',$sql);
38             my $row = $next->();
39             my $id = $row->[0];
40              
41             # hash iterator
42             my $next = $dbi->iterator('hash',$sql);
43             my $row = $next->();
44             my $id = $row->{id};
45              
46             $value = $dbi->quote($value);
47              
48             =head1 DESCRIPTION
49              
50             The DBUtils package is a wrapper around the database interface layer, providing
51             a collection of methods to access and alter the data within the database, which
52             handle any errors and abstracts these commonly called routines away from the
53             calling program.
54              
55             Known supported drivers:
56              
57             MySQL (database)
58             SQLite (database)
59             CSV (dbfile)
60             ODBC (driver)
61              
62             The keys in braces above, indicate how the name/location of the data store is
63             passed to the wrapper and thus added to the connection string.
64              
65             =cut
66              
67             # -------------------------------------
68             # Library Modules
69              
70 6     6   33 use Carp;
  6         10  
  6         928  
71 6     6   174004 use DBI;
  6         211828  
  6         985  
72              
73 6     6   81 use base qw(Class::Accessor::Fast);
  6         14  
  6         42894  
74              
75             # -------------------------------------
76             # The Public Interface Subs
77              
78             =head2 CONSTRUCTOR
79              
80             =over 4
81              
82             =item new()
83              
84             The Constructor method can be called with an anonymous hash,
85             listing the values to be used to connect to and handle the database.
86              
87             Values in the hash can be
88              
89             driver (*)
90             database (+)
91             dbfile (+)
92             dbhost
93             dbport
94             dbuser
95             dbpass
96             errsub
97             AutoCommit
98              
99             (*) These entries MUST exist in the hash.
100             (+) At least ONE of these must exist in the hash, and depend upon the driver.
101              
102             Note that 'dbfile' is for use with a flat file database, such as DBD::CSV.
103              
104             By default the errors are handle via croak(), however if you pass a subroutine
105             reference that will be called instead. Parameters passed to the error
106             subroutine are the error string, the SQL string and the list of arguments given.
107              
108             AutoCommit is on by default, unless you explicitly pass 'AutoCommit => 0'.
109              
110             =back
111              
112             =cut
113              
114             sub new {
115 1     1 1 1196241 my ($self, %hash) = @_;
116              
117             # check we've got our mandatory fields
118 1 50       23 croak("$self needs a driver!") unless($hash{driver});
119 1 50 33     31 croak("$self needs a database/file!")
120             unless($hash{database} || $hash{dbfile});
121              
122             # create an attributes hash
123 1 50 50     53 my $dbv = {
124             'driver' => $hash{driver},
125             'database' => $hash{database},
126             'dbfile' => $hash{dbfile},
127             'dbhost' => $hash{dbhost},
128             'dbport' => $hash{dbport},
129             'dbuser' => $hash{dbuser},
130             'dbpass' => $hash{dbpass},
131             'errsub' => $hash{errsub} || \&_errsub,
132             'AutoCommit' => defined $hash{AutoCommit} ? $hash{AutoCommit} : 1,
133             };
134              
135             # create the object
136 1         10 bless $dbv, $self;
137 1         8 return $dbv;
138             }
139              
140             =head2 PUBLIC INTERFACE METHODS
141              
142             =over 4
143              
144             =item get_query(type,sql,)
145              
146             type - 'array' or 'hash'
147             sql - SQL statement
148             - optional additional values to be inserted into SQL placeholders
149              
150             This method performs a SELECT statement and returns an array of the returned
151             rows. Each column within the row is then accessed as an array or hash as
152             specified by 'type'.
153              
154             =cut
155              
156             sub get_query {
157 6     6 1 1650 my ($dbv,$type,$sql,@args) = @_;
158 6 50       23 return () unless($sql);
159              
160             # if the object doesn't contain a reference to a dbh
161             # object then we need to connect to the database
162 6 50       23 $dbv = &_db_connect($dbv) if not $dbv->{dbh};
163              
164             # prepare the sql statement for executing
165 6         8 my $sth;
166 6         14 eval { $sth = $dbv->{dbh}->prepare($sql) };
  6         55  
167 6 50 33     714 if($@ || !$sth) {
168 0         0 $dbv->{errsub}->($dbv->{dbh}->errstr,$sql,@args);
169 0         0 return ();
170             }
171              
172             # execute the SQL using any values sent to the function
173             # to be placed in the sql
174 6         16 my $res;
175 6         55 eval { $res = $sth->execute(@args); };
  6         601  
176 6 50 33     44 if($@ || !$res) {
177 0         0 $dbv->{errsub}->($sth->errstr,$sql,@args);
178 0         0 return ();
179             }
180              
181 6         9 my @result;
182             # grab the data in the right way
183 6 100       19 if ( $type eq 'array' ) {
184 5         105 while ( my $row = $sth->fetchrow_arrayref() ) {
185 12         16 push @result, [@{$row}];
  12         160  
186             }
187             } else {
188 1         64 while ( my $row = $sth->fetchrow_hashref() ) {
189 1         18 push @result, $row;
190             }
191             }
192              
193             # finish with our statement handle
194 6         24 $sth->finish;
195             # return the found datastructure
196 6         146 return @result;
197             }
198              
199             =item iterator(type,sql,)
200              
201             type - 'array' or 'hash'
202             sql - SQL statement
203             - optional additional values to be inserted into SQL placeholders
204              
205             This method is used to call a SELECT statement a row at a time, via a closure.
206             Returns a subroutine reference which can then be used to obtain each row as a
207             array reference or hash reference. Finally returns 'undef' when no more rows
208             can be returned.
209              
210             =cut
211              
212             sub iterator {
213 2     2 1 1328 my ($dbv,$type,$sql,@args) = @_;
214 2 50       9 return unless($sql);
215              
216             # if the object doesn't contain a reference to a dbh
217             # object then we need to connect to the database
218 2 50       8 $dbv = &_db_connect($dbv) if not $dbv->{dbh};
219              
220             # prepare the sql statement for executing
221 2         4 my $sth;
222 2         4 eval { $sth = $dbv->{dbh}->prepare($sql); };
  2         32  
223 2 50 33     640 if($@ || !$sth) {
224 0         0 $dbv->{errsub}->($dbv->{dbh}->errstr,$sql,@args);
225 0         0 return;
226             }
227              
228             # execute the SQL using any values sent to the function
229             # to be placed in the sql
230 2         5 my $res;
231 2         3 eval { $res = $sth->execute(@args); };
  2         216  
232 2 50 33     17 if($@ || !$res) {
233 0         0 $dbv->{errsub}->($sth->errstr,$sql,@args);
234 0         0 return;
235             }
236              
237             # grab the data in the right way
238 2 100       9 if ( $type eq 'array' ) {
239             return sub {
240 9 100   9   6476 if ( my $row = $sth->fetchrow_arrayref() ) { return $row; }
  8         23  
241 1         5 else { $sth->finish; return; }
  1         3  
242             }
243 1         16 } else {
244             return sub {
245 9 100   9   11664 if ( my $row = $sth->fetchrow_hashref() ) { return $row; }
  8         25  
246 1         6 else { $sth->finish; return; }
  1         3  
247             }
248 1         13 }
249             }
250              
251             =item do_query(sql,)
252              
253             sql - SQL statement
254             - optional additional values to be inserted into SQL placeholders
255              
256             This method is used to perform an SQL action statement.
257              
258             =cut
259              
260             sub do_query {
261 9     9 1 2561 my ($dbv,$sql,@args) = @_;
262 9         53 $dbv->_do_query($sql,0,@args);
263             }
264              
265             =item id_query(sql,)
266              
267             sql - SQL statement
268             - optional additional values to be inserted into SQL placeholders
269              
270             This method is used to perform an SQL action statement. Commonly used when
271             performing an INSERT statement, so that it returns the inserted record id.
272              
273             =cut
274              
275             sub id_query {
276 0     0 1 0 my ($dbv,$sql,@args) = @_;
277 0         0 return $dbv->_do_query($sql,1,@args);
278             }
279              
280             # _do_query(sql,idrequired,)
281             #
282             # sql - SQL statement
283             # idrequired - true if an ID value is required on return
284             # - optional additional values to be inserted into SQL placeholders
285             #
286             # This method is used to perform an SQL action statement. Commonly used when
287             # performing an INSERT statement, so that it returns the inserted record id.
288              
289             sub _do_query {
290 9     9   41 my ($dbv,$sql,$idrequired,@args) = @_;
291 9         16 my $rowid;
292              
293 9 100       37 return unless($sql);
294              
295             # if the object doesn't contain a reference to a dbh
296             # object then we need to connect to the database
297 8 100       53 $dbv = &_db_connect($dbv) if not $dbv->{dbh};
298              
299 8 50       38 if($idrequired) {
300             # prepare the sql statement for executing
301 0         0 my $sth;
302 0         0 eval { $sth = $dbv->{dbh}->prepare($sql); };
  0         0  
303 0 0 0     0 if($@ || !$sth) {
304 0         0 $dbv->{errsub}->($dbv->{dbh}->errstr,$sql,@args);
305 0         0 return;
306             }
307              
308             # execute the SQL using any values sent to the function
309             # to be placed in the sql
310 0         0 my $res;
311 0         0 eval { $res = $sth->execute(@args); };
  0         0  
312 0 0 0     0 if($@ || !$res) {
313 0         0 $dbv->{errsub}->($sth->errstr,$sql,@args);
314 0         0 return;
315             }
316              
317 0 0       0 if($dbv->{driver} =~ /mysql/i) {
    0          
    0          
318 0         0 $rowid = $dbv->{dbh}->{mysql_insertid};
319             } elsif($dbv->{driver} =~ /pg/i) {
320 0         0 my ($table) = $sql =~ /INTO\s+(\S+)/;
321 0         0 $rowid = $dbv->{dbh}->last_insert_id(undef,undef,$table,undef);
322             } elsif($dbv->{driver} =~ /sqlite/i) {
323 0         0 $sth = $dbv->{dbh}->prepare('SELECT last_insert_rowid()');
324 0         0 $res = $sth->execute();
325 0         0 my $row;
326 0 0       0 $rowid = $row->[0] if( $row = $sth->fetchrow_arrayref() );
327             } else {
328 0         0 my $row;
329 0 0       0 $rowid = $row->[0] if( $row = $sth->fetchrow_arrayref() );
330             }
331              
332             } else {
333 8         22 eval { $dbv->{dbh}->do($sql, undef, @args) };
  8         97  
334 8 50       338482 if ( $@ ) {
335 0         0 $dbv->{errsub}->($dbv->{dbh}->errstr,$sql,@args);
336 0         0 return -1;
337             }
338              
339 8         29 $rowid = 1; # technically this should be the number of succesful rows
340             }
341              
342             ## Return the rowid we just used
343 8         79 return $rowid;
344             }
345              
346             =item repeat_query(sql,)
347              
348             sql - SQL statement
349             - values to be inserted into SQL placeholders
350              
351             This method is used to store an SQL action statement, together associated
352             arguments. Commonly used with statements where multiple arguments sets are
353             applied to the same statement.
354              
355             =item repeat_queries()
356              
357             This method performs all store SQL action statements.
358              
359             =item repeater(sql,)
360              
361             sql - SQL statement
362             - list of values to be inserted into SQL placeholders
363              
364             This method performs an single SQL action statement, using all the associated
365             arguments within the given list reference.
366              
367             =cut
368              
369             sub repeat_query {
370 5     5 1 692 my ($dbv,$sql,@args) = @_;
371 5 100 100     37 return unless($sql && @args);
372              
373             # if the object doesn't contain a reference to a dbh
374             # object then we need to connect to the database
375 3 50       10 $dbv = &_db_connect($dbv) if not $dbv->{dbh};
376              
377 3         5 push @{ $dbv->{repeat}{$sql} }, \@args;
  3         24  
378             }
379              
380             sub repeat_queries {
381 2     2 1 1359 my $dbv = shift;
382 2 100 66     24 return unless($dbv && $dbv->{repeat});
383              
384 1         2 for my $sql (keys %{ $dbv->{repeat} }) {
  1         8  
385 1         6 $dbv->repeater($sql,$dbv->{repeat}{$sql});
386             }
387              
388 1         9 $dbv->{repeat} = undef;
389             }
390              
391             sub repeater {
392 1     1 1 3 my ($dbv,$sql,$args) = @_;
393 1         3 my $rows = 0;
394              
395 1 50       5 return $rows unless($sql);
396              
397             # if the object doesn't contain a reference to a dbh
398             # object then we need to connect to the database
399 1 50       5 $dbv = &_db_connect($dbv) if not $dbv->{dbh};
400              
401             # prepare the sql statement for executing
402 1         2 my $sth;
403 1         2 eval { $sth = $dbv->{dbh}->prepare($sql); };
  1         10  
404 1 50 33     114 if($@ || !$sth) {
405 0         0 $dbv->{errsub}->($dbv->{dbh}->errstr,$sql,@{$args->[0]});
  0         0  
406 0         0 return $rows;
407             }
408              
409 1         6 for my $arg (@$args) {
410             # execute the SQL using any values sent to the function
411             # to be placed in the sql
412 3         6 my $res;
413 3         34 eval { $res = $sth->execute(@$arg); };
  3         57300  
414 3 50 33     57 if($@ || !$res) {
415 0         0 $dbv->{errsub}->($sth->errstr,$sql,@$args);
416 0         0 next;
417             }
418              
419 3         16 $rows++;
420             }
421              
422 1         94 return $rows;
423             }
424              
425             =item do_commit()
426              
427             Performs a commit on the transaction where AutoCommit is disabled.
428              
429             =cut
430              
431             sub do_commit {
432 0     0 1 0 my $dbv = shift;
433 0 0       0 $dbv->{dbh}->commit if($dbv->{dbh});
434             }
435              
436             =item quote(string)
437              
438             string - string to be quoted
439              
440             This method performs a DBI quote operation, which will quote a string
441             according to the SQL rules.
442              
443             =cut
444              
445             sub quote {
446 1     1 1 594 my $dbv = shift;
447 1 50       13 return unless($_[0]);
448              
449             # Cant quote with DBD::CSV
450 1 50       50 return $_[0] if($dbv->{driver} =~ /csv/i);
451              
452             # if the object doesnt contain a reference to a dbh object
453             # then we need to connect to the database
454 1 50       5 $dbv = &_db_connect($dbv) if not $dbv->{dbh};
455              
456 1         28 $dbv->{dbh}->quote($_[0]);
457             }
458              
459             # -------------------------------------
460             # The Accessors
461              
462             =item Accessor Methods
463              
464             The following accessor methods are available:
465              
466             =over 4
467              
468             =item * driver
469              
470             =item * database
471              
472             =item * dbfile
473              
474             =item * dbhost
475              
476             =item * dbport
477              
478             =item * dbuser
479              
480             =item * dbpass
481              
482             =back
483              
484             All methods can be called to return the current value of the associated
485             object variable. Note that these are only meant to be used as read-only
486             methods.
487              
488             =cut
489              
490             __PACKAGE__->mk_accessors(qw(driver database dbfile dbhost dbport dbuser dbpass));
491              
492             # -------------------------------------
493             # The Private Subs
494             # These modules should not have to be called from outside this module
495              
496             sub _db_connect {
497 1     1   4 my $dbv = shift;
498              
499 1         8 my $dsn = 'dbi:' . $dbv->{driver};
500 1         12 my %options = (
501             RaiseError => 1,
502             AutoCommit => $dbv->{AutoCommit},
503             );
504              
505 1 50       19 if($dbv->{driver} =~ /ODBC/) {
    50          
506             # all the info is in the Data Source repository
507              
508             } elsif($dbv->{driver} =~ /SQLite/i) {
509 1 50       7 $dsn .= ':dbname=' . $dbv->{database} if $dbv->{database};
510 1 50       11 $dsn .= ';host=' . $dbv->{dbhost} if $dbv->{dbhost};
511 1 50       11 $dsn .= ';port=' . $dbv->{dbport} if $dbv->{dbport};
512              
513 1         7 $options{sqlite_handle_binary_nulls} = 1;
514              
515             } else {
516 0 0       0 $dsn .= ':f_dir=' . $dbv->{dbfile} if $dbv->{dbfile};
517 0 0       0 $dsn .= ':database=' . $dbv->{database} if $dbv->{database};
518 0 0       0 $dsn .= ';host=' . $dbv->{dbhost} if $dbv->{dbhost};
519 0 0       0 $dsn .= ';port=' . $dbv->{dbport} if $dbv->{dbport};
520             }
521              
522 1         5 eval {
523 1         14 $dbv->{dbh} = DBI->connect($dsn, $dbv->{dbuser}, $dbv->{dbpass}, \%options);
524             };
525              
526 1 50       673 croak("Cannot connect to DB [$dsn]: $@") if($@);
527 1         9 return $dbv;
528             }
529              
530             sub DESTROY {
531 1     1   659 my $dbv = shift;
532             # $dbv->{dbh}->commit if defined $dbv->{dbh};
533 1 50       569 $dbv->{dbh}->disconnect if defined $dbv->{dbh};
534             }
535              
536             sub _errsub {
537 0     0     my ($err,$sql,@args) = @_;
538 0 0         croak("err=$err, sql=[$sql], args[".join(",",map{$_ || ''} @args)."]");
  0            
539             }
540              
541             1;
542              
543             __END__