File Coverage

blib/lib/CPAN/Testers/Common/DBUtils.pm
Criterion Covered Total %
statement 150 175 85.7
branch 64 100 64.0
condition 19 35 54.2
subroutine 23 23 100.0
pod 11 11 100.0
total 267 344 77.6


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