File Coverage

blib/lib/Alzabo/Driver.pm
Criterion Covered Total %
statement 39 313 12.4
branch 0 124 0.0
condition 0 12 0.0
subroutine 13 58 22.4
pod 25 28 89.2
total 77 535 14.3


line stmt bran cond sub pod time code
1             package Alzabo::Driver;
2              
3 12     12   40720 use strict;
  12         27  
  12         451  
4 12     12   64 use vars qw($VERSION);
  12         24  
  12         482  
5              
6 12     12   706 use Alzabo::Exceptions;
  12         35  
  12         105  
7              
8 12     12   17411 use Class::Factory::Util;
  12         8752  
  12         78  
9 12     12   36515 use DBI;
  12         301767  
  12         1169  
10 12     12   1523 use Params::Validate qw( validate validate_pos UNDEF SCALAR ARRAYREF );
  12         30181  
  12         19654  
11             Params::Validate::validation_options( on_fail => sub { Alzabo::Exception::Params->throw( error => join '', @_ ) } );
12              
13             $VERSION = 2.0;
14              
15             1;
16              
17             sub new
18             {
19 0     0 1   shift;
20 0           my %p = @_;
21              
22 0           eval "use Alzabo::Driver::$p{rdbms}";
23 0 0         Alzabo::Exception::Eval->throw( error => $@ ) if $@;
24              
25 0           my $self = "Alzabo::Driver::$p{rdbms}"->new(@_);
26              
27 0           $self->{schema} = $p{schema};
28              
29 0           return $self;
30             }
31              
32 0     0 1   sub available { __PACKAGE__->subclasses }
33              
34             sub _ensure_valid_dbh
35             {
36 0     0     my $self = shift;
37              
38 0 0         unless ( $self->{dbh} )
39             {
40 0           my $sub = (caller(1))[3];
41 0           Alzabo::Exception::Driver->throw( error => "Cannot call $sub before calling connect." );
42             }
43              
44 0 0         $self->{dbh} = $self->_dbi_connect( $self->{connect_params} )
45             if $$ != $self->{connect_pid};
46             }
47              
48             sub quote
49             {
50 0     0 1   my $self = shift;
51              
52 0           $self->_ensure_valid_dbh;
53              
54 0           return $self->{dbh}->quote(@_);
55             }
56              
57             sub quote_identifier
58             {
59 0     0 1   my $self = shift;
60              
61 0           $self->_ensure_valid_dbh;
62              
63 0           return $self->{dbh}->quote_identifier(@_);
64             }
65              
66             sub rows
67             {
68 0     0 1   my $self = shift;
69              
70 0           $self->_ensure_valid_dbh;
71              
72 0           my %p = @_;
73              
74 0           my $sth = $self->_prepare_and_execute(%p);
75              
76 0           my @data;
77             eval
78 0           {
79 0           my @row;
80 0           $sth->bind_columns( \ (@row[ 0..$#{ $sth->{NAME_lc} } ] ) );
  0            
81              
82 0           push @data, [@row] while $sth->fetch;
83              
84 0           $sth->finish;
85             };
86 0 0         if ($@)
87             {
88 0 0         my @bind = exists $p{bind} ? ( ref $p{bind} ? $p{bind} : [$p{bind}] ) : ();
    0          
89 0           Alzabo::Exception::Driver->throw( error => $@,
90             sql => $p{sql},
91             bind => \@bind );
92             }
93              
94 0 0         return wantarray ? @data : $data[0];
95             }
96              
97             sub rows_hashref
98             {
99 0     0 1   my $self = shift;
100 0           my %p = @_;
101              
102 0           $self->_ensure_valid_dbh;
103              
104 0           my $sth = $self->_prepare_and_execute(%p);
105              
106 0           my @data;
107              
108             eval
109 0           {
110 0           my %hash;
111 0           $sth->bind_columns( \ ( @hash{ @{ $sth->{NAME_uc} } } ) );
  0            
112              
113 0           push @data, {%hash} while $sth->fetch;
114              
115 0           $sth->finish;
116             };
117 0 0         if ($@)
118             {
119 0 0         my @bind = exists $p{bind} ? ( ref $p{bind} ? $p{bind} : [$p{bind}] ) : ();
    0          
120 0           Alzabo::Exception::Driver->throw( error => $@,
121             sql => $p{sql},
122             bind => \@bind );
123             }
124              
125 0           return @data;
126             }
127              
128             sub one_row
129             {
130 0     0 1   my $self = shift;
131 0           my %p = @_;
132              
133 0           $self->_ensure_valid_dbh;
134              
135 0           my $sth = $self->_prepare_and_execute(%p);
136              
137 0           my @row;
138             eval
139 0           {
140 0           @row = $sth->fetchrow_array;
141 0           $sth->finish;
142             };
143 0 0         if ($@)
144             {
145 0 0         my @bind = exists $p{bind} ? ( ref $p{bind} ? $p{bind} : [$p{bind}] ) : ();
    0          
146 0           Alzabo::Exception::Driver->throw( error => $@,
147             sql => $p{sql},
148             bind => \@bind );
149             }
150              
151 0 0         return wantarray ? @row : $row[0];
152             }
153              
154             sub one_row_hash
155             {
156 0     0 1   my $self = shift;
157 0           my %p = @_;
158              
159 0           $self->_ensure_valid_dbh;
160              
161 0           my $sth = $self->_prepare_and_execute(%p);
162              
163 0           my %hash;
164             eval
165 0           {
166 0           my @row = $sth->fetchrow_array;
167 0 0         @hash{ @{ $sth->{NAME_uc} } } = @row if @row;
  0            
168 0           $sth->finish;
169             };
170 0 0         if ($@)
171             {
172 0 0         my @bind = exists $p{bind} ? ( ref $p{bind} ? $p{bind} : [$p{bind}] ) : ();
    0          
173 0           Alzabo::Exception::Driver->throw( error => $@,
174             sql => $p{sql},
175             bind => \@bind );
176             }
177              
178 0           return %hash;
179             }
180              
181             sub column
182             {
183 0     0 1   my $self = shift;
184 0           my %p = @_;
185              
186 0           $self->_ensure_valid_dbh;
187              
188 0           my $sth = $self->_prepare_and_execute(%p);
189              
190 0           my @data;
191             eval
192 0           {
193 0           my @row;
194 0           $sth->bind_columns( \ (@row[ 0..$#{ $sth->{NAME_lc} } ] ) );
  0            
195 0           push @data, $row[0] while ($sth->fetch);
196 0           $sth->finish;
197             };
198 0 0         if ($@)
199             {
200 0 0         my @bind = exists $p{bind} ? ( ref $p{bind} ? $p{bind} : [$p{bind}] ) : ();
    0          
201 0           Alzabo::Exception::Driver->throw( error => $@,
202             sql => $p{sql},
203             bind => \@bind );
204             }
205              
206 0 0         return wantarray ? @data : $data[0];
207             }
208              
209 12         29486 use constant _PREPARE_AND_EXECUTE_SPEC => { sql => { type => SCALAR },
210             bind => { type => UNDEF | SCALAR | ARRAYREF,
211             optional => 1 },
212 12     12   109 };
  12         26  
213              
214             sub _prepare_and_execute
215             {
216 0     0     my $self = shift;
217              
218 0           validate( @_, _PREPARE_AND_EXECUTE_SPEC );
219 0           my %p = @_;
220              
221 0 0         Alzabo::Exception::Driver->throw( error => "Attempt to access the database without database handle. Was ->connect called?" )
222             unless $self->{dbh};
223              
224 0 0         my @bind = exists $p{bind} ? ( ref $p{bind} ? @{ $p{bind} } : $p{bind} ) : ();
  0 0          
225              
226 0           my $sth;
227             eval
228 0           {
229 0           $sth = $self->{dbh}->prepare( $p{sql} );
230 0           $sth->execute(@bind);
231             };
232 0 0         if ($@)
233             {
234 0           Alzabo::Exception::Driver->throw( error => $@,
235             sql => $p{sql},
236             bind => \@bind );
237             }
238              
239 0           return $sth;
240             }
241              
242             sub do
243             {
244 0     0 1   my $self = shift;
245 0           my %p = @_;
246              
247 0           $self->_ensure_valid_dbh;
248              
249 0           my $sth = $self->_prepare_and_execute(%p);
250              
251 0           my $rows;
252             eval
253 0           {
254 0           $rows = $sth->rows;
255 0           $sth->finish;
256             };
257 0 0         if ($@)
258             {
259 0 0         my @bind = exists $p{bind} ? ( ref $p{bind} ? $p{bind} : [$p{bind}] ) : ();
    0          
260 0           Alzabo::Exception::Driver->throw( error => $@,
261             sql => $p{sql},
262             bind => \@bind );
263             }
264              
265 0           return $rows;
266             }
267              
268             sub tables
269             {
270 0     0 1   my $self = shift;
271              
272 0           $self->_ensure_valid_dbh;
273              
274 0           my @t = eval { $self->{dbh}->tables( '', '', '%', 'table' ); };
  0            
275 0 0         Alzabo::Exception::Driver->throw( error => $@ ) if $@;
276              
277 0           return @t;
278             }
279              
280             sub schemas
281             {
282 0     0 1   my $self = shift;
283              
284 0           shift()->_virtual;
285             }
286              
287             sub _make_dbh
288             {
289 0     0     my $self = shift;
290              
291 0           return $self->_dbi_connect( $self->_connect_params(@_) );
292             }
293              
294             sub _dbi_connect
295             {
296 0     0     my $self = shift;
297 0           my $connect = shift;
298              
299 0           my $dbh = eval { DBI->connect(@$connect) };
  0            
300              
301 0 0         Alzabo::Exception::Driver->throw( error => $@ ) if $@;
302 0 0         Alzabo::Exception::Driver->throw( error => "Unable to connect to database\n" ) unless $dbh;
303              
304 0           $self->{connect_params} = $connect;
305 0           $self->{connect_pid} = $$;
306              
307 0           return $dbh;
308             }
309              
310             sub statement
311             {
312 0     0 1   my $self = shift;
313              
314 0           $self->_ensure_valid_dbh;
315              
316 0           return Alzabo::DriverStatement->new( dbh => $self->{dbh},
317             @_ );
318             }
319              
320             sub statement_no_execute
321             {
322 0     0 0   my $self = shift;
323              
324 0           $self->_ensure_valid_dbh;
325              
326 0           return Alzabo::DriverStatement->new_no_execute( dbh => $self->{dbh},
327             @_ );
328             }
329              
330             sub func
331             {
332 0     0 0   my $self = shift;
333              
334 0           $self->_ensure_valid_dbh;
335              
336 0           my @r;
337             eval
338 0           {
339 0 0         if (wantarray)
340             {
341 0           @r = $self->{dbh}->func(@_);
342 0           return @r;
343             }
344             else
345             {
346 0           $r[0] = $self->{dbh}->func(@_);
347 0           return $r[0];
348             }
349             };
350 0 0         Alzabo::Exception::Driver->throw( error => $self->{dbh}->errstr )
351             if $self->{dbh}->errstr;
352             }
353              
354             sub DESTROY
355             {
356 0     0     my $self = shift;
357 0           $self->disconnect;
358             }
359              
360             sub disconnect
361             {
362 0     0 0   my $self = shift;
363 0 0         $self->{dbh}->disconnect if $self->{dbh};
364 0           delete $self->{dbh};
365             }
366              
367             sub handle
368             {
369 0     0 1   my $self = shift;
370              
371 0 0         if (@_)
372             {
373 0           validate_pos( @_, { isa => 'DBI::db' } );
374 0           $self->{dbh} = shift;
375             }
376              
377 0           return $self->{dbh};
378             }
379              
380             sub rdbms_version
381             {
382 0     0 1   shift()->_virtual;
383             }
384              
385             sub connect
386             {
387 0     0 1   shift()->_virtual;
388             }
389              
390             sub supports_referential_integrity
391             {
392 0     0 1   shift()->_virtual;
393             }
394              
395             sub create_database
396             {
397 0     0 1   shift()->_virtual;
398             }
399              
400             sub drop_database
401             {
402 0     0 1   shift()->_virtual;
403             }
404              
405             sub next_sequence_number
406             {
407 0     0 1   shift()->_virtual;
408             }
409              
410             sub begin_work
411             {
412 0     0 1   my $self = shift;
413              
414 0           $self->_ensure_valid_dbh;
415              
416 0 0         $self->{tran_count} = 0 unless defined $self->{tran_count};
417              
418 0 0         $self->{dbh}->begin_work if $self->{dbh}->{AutoCommit};
419              
420 0           $self->{tran_count}++;
421             }
422              
423             sub rollback
424             {
425 0     0 1   my $self = shift;
426              
427 0           $self->_ensure_valid_dbh;
428              
429 0           $self->{tran_count} = undef;
430              
431 0 0         eval { $self->{dbh}->rollback unless $self->{dbh}->{AutoCommit} };
  0            
432              
433 0 0         Alzabo::Exception::Driver->throw( error => $@ ) if $@;
434              
435 0           $self->{dbh}->{AutoCommit} = 1;
436             }
437              
438             sub commit
439             {
440 0     0 1   my $self = shift;
441              
442 0           $self->_ensure_valid_dbh;
443              
444 0           my $callee = (caller(1))[3];
445              
446             # More commits than begin_tran. Not correct.
447 0 0         if ( defined $self->{tran_count} )
448             {
449 0           $self->{tran_count}--;
450             }
451             else
452             {
453 0           my $caller = (caller(1))[3];
454 0           require Carp;
455 0           Carp::cluck( "$caller called commit without corresponding begin_work call\n" );
456             }
457              
458             # Don't actually commit until we reach 'uber-commit'
459 0 0         return if $self->{tran_count};
460              
461 0 0         unless ( $self->{dbh}->{AutoCommit} )
462             {
463 0           $self->{dbh}->commit;
464             }
465 0           $self->{dbh}->{AutoCommit} = 1;
466              
467 0           $self->{tran_count} = undef;
468             }
469              
470             sub get_last_id
471             {
472 0     0 1   shift()->_virtual;
473             }
474              
475             sub driver_id
476             {
477 0     0 1   shift()->_virtual;
478             }
479              
480             sub _virtual
481             {
482 0     0     my $self = shift;
483              
484 0           my $sub = (caller(1))[3];
485 0           Alzabo::Exception::VirtualMethod->throw( error =>
486             "$sub is a virtual method and must be subclassed in " . ref $self );
487             }
488              
489             package Alzabo::DriverStatement;
490              
491 12     12   117 use strict;
  12         36  
  12         581  
492 12     12   71 use vars qw($VERSION);
  12         24  
  12         617  
493              
494 12     12   82 use Alzabo::Exceptions;
  12         25  
  12         159  
495              
496 12     12   78 use DBI;
  12         45  
  12         676  
497              
498 12     12   75 use Params::Validate qw( validate UNDEF SCALAR ARRAYREF );
  12         24  
  12         2754  
499             Params::Validate::validation_options( on_fail => sub { Alzabo::Exception::Params->throw( error => join '', @_ ) } );
500              
501             $VERSION = '0.1';
502              
503             sub new
504             {
505 0     0     my $self = shift->new_no_execute(@_);
506              
507 0           $self->execute;
508              
509 0           return $self;
510             }
511              
512 12         22742 use constant NEW_NO_EXECUTE_SPEC => { dbh => { can => 'prepare' },
513             sql => { type => SCALAR },
514             bind => { type => SCALAR | ARRAYREF,
515             optional => 1 },
516             limit => { type => UNDEF | ARRAYREF,
517             optional => 1 },
518 12     12   85 };
  12         25  
519              
520             sub new_no_execute
521             {
522 0     0     my $proto = shift;
523 0   0       my $class = ref $proto || $proto;
524              
525 0           my %p = validate( @_, NEW_NO_EXECUTE_SPEC );
526              
527 0           my $self = bless {}, $class;
528              
529 0 0         $self->{limit} = $p{limit} ? $p{limit}[0] : 0;
530 0 0 0       $self->{offset} = $p{limit} && $p{limit}[1] ? $p{limit}[1] : 0;
531 0           $self->{rows_fetched} = 0;
532              
533             eval
534 0           {
535 0           $self->{sth} = $p{dbh}->prepare( $p{sql} );
536              
537 0 0         $self->{bind} = exists $p{bind} ? ( ref $p{bind} ? $p{bind} : [ $p{bind} ] ) : [];
    0          
538             };
539              
540 0 0         Alzabo::Exception::Driver->throw( error => $@,
541             sql => $p{sql},
542             bind => $self->{bind} ) if $@;
543              
544 0           return $self;
545             }
546              
547             sub execute
548             {
549 0     0     my $self = shift;
550              
551             eval
552 0           {
553 0 0         $self->{sth}->finish if $self->{sth}->{Active};
554 0           $self->{rows_fetched} = 0;
555 0 0         $self->{sth}->execute( @_ ? @_ : @{ $self->{bind} } );
  0            
556              
557 0           $self->{result} = [];
558 0           $self->{count} = 0;
559              
560 0           $self->{sth}->bind_columns
561 0           ( \ ( @{ $self->{result} }[ 0..$#{ $self->{sth}->{NAME_lc} } ] ) );
  0            
562             };
563 0 0         Alzabo::Exception::Driver->throw( error => $@,
564             sql => $self->{sth}{Statement},
565             bind => $self->{bind} ) if $@;
566             }
567              
568             sub execute_no_result
569             {
570 0     0     my $self = shift;
571              
572             eval
573 0           {
574 0           $self->{sth}->execute(@_);
575             };
576 0 0         Alzabo::Exception::Driver->throw( error => $@,
577             sql => $self->{sth}{Statement},
578             bind => $self->{bind} ) if $@;
579             }
580              
581             sub next
582             {
583 0     0     my $self = shift;
584 0           my %p = @_;
585              
586 0 0         return unless $self->{sth}->{Active};
587              
588 0           my $active;
589             eval
590 0           {
591             do
592 0   0       {
593 0           $active = $self->{sth}->fetch;
594             } while ( $active && $self->{rows_fetched}++ < $self->{offset} );
595             };
596              
597 0 0         Alzabo::Exception::Driver->throw( error => $@,
598             sql => $self->{sth}{Statement},
599             bind => $self->{bind} ) if $@;
600              
601 0 0         return unless $active;
602              
603 0           $self->{count}++;
604              
605 0 0         return wantarray ? @{ $self->{result} } : $self->{result}[0];
  0            
606             }
607              
608             sub next_as_hash
609             {
610 0     0     my $self = shift;
611              
612 0 0         return unless $self->{sth}->{Active};
613              
614 0           my $active;
615             eval
616 0           {
617             do
618 0   0       {
619 0           $active = $self->{sth}->fetch;
620             } while ( $active && $self->{rows_fetched}++ < $self->{offset} );
621             };
622 0 0         Alzabo::Exception::Driver->throw( error => $@,
623             sql => $self->{sth}{Statement},
624             bind => $self->{bind} ) if $@;
625              
626 0 0         return unless $active;
627              
628 0           my %hash;
629 0           @hash{ @{ $self->{sth}->{NAME_lc} } } = @{ $self->{result} };
  0            
  0            
630              
631 0           $self->{count}++;
632              
633 0           return %hash;
634             }
635             *next_hash = \&next_as_hash;
636              
637             sub all_rows
638             {
639 0     0     my $self = shift;
640              
641 0           my @rows;
642              
643 0           while (my @row = $self->next)
644             {
645 0 0         push @rows, @row > 1 ? \@row : $row[0];
646             }
647              
648 0           $self->{count} = scalar @rows;
649              
650 0           return @rows;
651             }
652              
653             sub all_rows_hash
654             {
655 0     0     my $self = shift;
656              
657 0           my @rows;
658              
659 0           while (my %h = $self->next_as_hash)
660             {
661 0           push @rows, \%h;
662             }
663              
664 0           $self->{count} = scalar @rows;
665              
666 0           return @rows;
667             }
668              
669             sub bind
670             {
671 0     0     my $self = shift;
672              
673 0           return @{ $self->{bind} };
  0            
674             }
675              
676 0     0     sub count { $_[0]->{count} }
677              
678             sub DESTROY
679             {
680 0     0     my $self = shift;
681              
682 0           local $@;
683 0 0         eval { $self->{sth}->finish if $self->{sth}; };
  0            
684 0 0         Alzabo::Exception::Driver->throw( error => $@ ) if $@;
685             }
686              
687             1;
688              
689             __END__