File Coverage

lib/DBIx/AnyDBD.pm
Criterion Covered Total %
statement 21 127 16.5
branch 0 64 0.0
condition 0 14 0.0
subroutine 7 17 41.1
pod 4 7 57.1
total 32 229 13.9


line stmt bran cond sub pod time code
1             # $Id: AnyDBD.pm,v 1.15 2002/09/04 12:05:03 matt Exp $
2              
3             package DBIx::AnyDBD;
4 1     1   2248 use DBI;
  1         17680  
  1         65  
5 1     1   10 use warnings;
  1         2  
  1         78  
6 1     1   6 use strict;
  1         3  
  1         25  
7 1     1   5 use vars qw/$AUTOLOAD $VERSION/;
  1         2  
  1         495  
8              
9             =head1 NAME
10              
11             DBIx::AnyDBD - DBD independent class
12              
13             =head1 VERSION
14              
15             Version 2.03
16              
17             =cut
18              
19             our $VERSION = '2.03';
20              
21             =head1 SYNOPSIS
22              
23             This class provides application developers with an abstraction class
24             a level away from DBI, that allows them to write an application that
25             works on multiple database platforms. The idea isn't to take away the
26             responsibility for coding different SQL on different platforms, but
27             to simply provide a platform that uses the right class at the right
28             time for whatever DB is currently in use.
29              
30              
31             use DBIx::AnyDBD;
32            
33             my $db = DBIx::AnyDBD->connect("dbi:Oracle:sid1",
34             "user", "pass", {}, "MyClass");
35              
36             my $foo = $db->foo;
37             my $blee = $db->blee;
38              
39             That doesn't really tell you much... Because you have to implement a
40             bit more than that. Underneath you have to have a module
41             MyClass::Oracle that has methods foo() and blee in it. If those
42             methods don't exist in MyClass::Oracle, it will check in MyClass::Default,
43             allowing you to implement code that doesn't need to be driver
44             dependent in the same module. The foo() and blee() methods will receive
45             the DBIx::AnyDBD instance as thier first parameter, and any parameters
46             you pass just go as parameters.
47              
48             See the example Default.pm and Sybase.pm classes in the AnyDBD directory
49             for an example.
50              
51             Underneath it's all implemented using the ISA hierarchy, which is modified
52             when you connect to your database. The inheritance tree ensures that the
53             right functions get called at the right time. There is also an AUTOLOADer
54             that steps in if the function doesn't exist and tries to call the function
55             on the database handle (i.e. in the DBI class). The sub-classing uses
56             C{Driver}->{Name})> (along with some clever fiddling for
57             ODBC and ADO) to get the super-class, so if you don't know what to name
58             your class (see the list below first) then check that.
59              
60             =head1 SUBROUTINES/METHODS
61              
62             =head2 new
63              
64             dsn => $dsn,
65             user => $user,
66             pass => $pass,
67             attr => $attr,
68             package => $package
69              
70             new() is a named parameter call that connects and creates a new db object
71             for your use. The named parameters are dsn, user, pass, attr and package.
72             The first 4 are just the parameters passed to DBI->connect, and package
73             contains the package prefix for your database dependent modules, for example,
74             if package was "MyPackage", the AUTOLOADer would look for
75             MyPackage::Oracle::func, and then MyPackage::Default::func. Beware that the
76             DBD driver will be ucfirst'ed, because lower case package names are reserved
77             as pragmas in perl. See the known DBD package mappings below.
78              
79             If package parameter is undefined then the package name used to call
80             the constructor is used. This will usually be DBIx::AnyDBD. This, in
81             itself, is not very useful but is convenient if you subclass
82             DBIx::AnyDBD.
83              
84             If attr is undefined then the default attributes are:
85              
86             AutoCommit => 1
87             PrintError => 0
88             RaiseError => 1
89              
90             So be aware if you don't want your application dying to either eval{} all
91             db sections and catch the exception, or pass in a different attr parameter.
92              
93             After re-blessing the object into the database specific object, DBIx::AnyDBD
94             will call the _init() method on the object, if it exists. This allows you
95             to perform some driver specific post-initialization.
96              
97             =cut
98              
99             sub new {
100 0     0 1   my $proto = shift;
101 0           my %args = @_;
102              
103 0   0       my $class = ref($proto) || $proto;
104              
105 0 0         return unless(defined($class));
106              
107             my $dbh = DBI->connect(
108             $args{dsn},
109             $args{user},
110             $args{pass},
111             ($args{attr} ||
112             {
113 0   0       AutoCommit => 1,
114             PrintError => 0,
115             RaiseError => 1,
116             })
117             );
118 0 0         die "Can't connect: " . DBI->errstr unless $dbh;
119 0   0       my $package = $args{'package'} || $class;
120 0           my $self = bless { 'package' => $package, dbh => $dbh }, $class;
121 0           $self->rebless;
122 0 0         $self->_init if $self->can('_init');
123 0           return $self;
124             }
125              
126             =head2 new_with_dbh
127              
128             Instantiate an object around an existing DBI database handle.
129              
130             =cut
131              
132             # RT#5661
133             sub new_with_dbh {
134 0     0 1   my ($class, $dbh, $package) = @_;
135 0           my $self = bless { 'package' => $package, 'dbh' => $dbh }, $class;
136              
137 0           $self->rebless;
138 0 0         $self->_init if $self->can('_init');
139            
140 0           return $self;
141             }
142              
143             =head2 connect($dsn, $user, $pass, $attr, $package)
144              
145             connect() is very similar to DBI->connect, taking exactly the same first
146             4 parameters. The 5th parameter is the package prefix, as above.
147              
148             connect() doesn't try and default attributes for you if you don't pass them.
149              
150             After re-blessing the object into the database specific object, DBIx::AnyDBD
151             will call the _init() method on the object, if it exists. This allows you
152             to perform some driver specific post-initialization.
153              
154             =cut
155              
156             sub connect {
157 0     0 1   my $class = shift;
158 0           my ($dsn, $user, $pass, $args, $package) = @_;
159 0           my $dbh = DBI->connect($dsn, $user, $pass, $args);
160 0 0         return undef unless $dbh;
161 0   0       $package ||= $class;
162 0           my $self = bless { 'package' => $package, 'dbh' => $dbh }, $class;
163 0           $self->rebless;
164 0 0         $self->_init if $self->can('_init');
165 0           return $self;
166             }
167              
168             sub rebless {
169 0     0 0   my $self = shift;
170 0           my $driver = ucfirst($self->{dbh}->{Driver}->{Name});
171 0 0         if ( $driver eq 'Proxy' ) {
172             # Looking into the internals of DBD::Proxy is maybe a little questionable
173 0           ($driver) = $self->{dbh}->{proxy_client}->{application} =~ /^DBI:(.+?):/;
174             }
175 0           my $class = $self->{'package'};
176 0           my ($odbc, $ado) = ($driver eq 'ODBC', $driver eq 'ADO');
177 0 0 0       if ($odbc || $ado) {
178 0           my $name;
179            
180 0 0         if ($odbc) {
    0          
181 1     1   8 no strict;
  1         2  
  1         599  
182 0           $name = $self->{dbh}->func(17, GetInfo);
183             }
184             elsif ($ado) {
185 0           $name = $self->{dbh}->{ado_conn}->Properties->Item('DBMS Name')->Value;
186             }
187             else {
188 0           die "Can't determine driver name!\n";
189             }
190            
191 0 0         if ($name eq 'Microsoft SQL Server') {
    0          
    0          
    0          
    0          
192 0           $driver = 'MSSQL';
193             }
194             elsif ($name eq 'SQL Server') {
195 0           $driver = 'Sybase';
196             }
197             elsif ($name =~ /Oracle/) {
198 0           $driver = 'Oracle';
199             }
200             # elsif ($name eq 'ACCESS') {
201             # $driver = 'Access';
202             # }
203             # elsif ($name eq 'Informix') {
204             # $driver = 'Informix'; # caught by "else" condition below
205             # }
206             elsif ($name eq 'Adaptive Server Anywhere') {
207 0           $driver = 'ASAny';
208             } elsif ($name eq 'ADABAS D') {
209 0           $driver = 'AdabasD';
210             }
211             else { # this should catch Access and Informix
212 0           $driver = lc($name);
213 0           $driver =~ s/\b(\w)/uc($1)/eg;
  0            
214 0           $driver =~ s/\s+/_/g;
215             }
216             }
217            
218 0           my $dir;
219 0           ($dir = $self->{package}) =~ s/::/\//g;
220 0 0         load_module("$dir/Default.pm") or die "Cannot find $dir/Default.pm module in \@INC for $self->{package}!";
221              
222 0 0         if (!load_module("$dir/$driver.pm")) {
223             # no package for driver - use Default instead
224 0           bless $self, "${class}::Default";
225             # make Default -> DBIx::AnyDBD hierarchy
226 0           add_isa("${class}::Default", 'DBIx::AnyDBD');
227             }
228             else {
229             # package OK...
230            
231 0           bless $self, "${class}::${driver}";
232            
233 0 0         if ($ado) {
234 0 0         if (load_module("$dir/ADO.pm")) {
235 0 0         if (!load_module("$dir/ODBC.pm")) {
236 0           add_isa("${class}::${driver}", "${class}::ADO");
237 0           add_isa("${class}::ADO", "${class}::Default");
238             }
239             else {
240 0           add_isa("${class}::${driver}", "${class}::ADO");
241 0           add_isa("${class}::ADO", "${class}::ODBC");
242 0           add_isa("${class}::ODBC", "${class}::Default");
243             }
244 0           return;
245             }
246             }
247            
248 0 0         if ($odbc) {
249 0 0         if (load_module("$dir/ODBC.pm")) {
250 0           add_isa("${class}::${driver}", "${class}::ODBC");
251 0           add_isa("${class}::ODBC", "${class}::Default");
252 0           return;
253             }
254             }
255            
256             # make Default -> DBIx::AnyDBD hierarchy
257 0           add_isa("${class}::Default", 'DBIx::AnyDBD');
258            
259             # make Driver -> Default hierarchy
260 0           add_isa("${class}::${driver}", "${class}::Default");
261             }
262            
263             }
264              
265             sub add_isa {
266 0     0 0   my ($class, $newval) = @_;
267 1     1   7 no strict 'refs';
  1         2  
  1         254  
268 0 0         unshift @{"${class}::ISA"}, $newval unless $class->isa($newval);
  0            
269             }
270              
271             sub load_module {
272 0     0 0   my $module = shift;
273            
274 0           eval {
275 0           require $module;
276             };
277 0 0         if ($@) {
278 0 0         if ($@ =~ /^Can't locate $module in \@INC/) {
279 0           undef $@;
280 0           return 0;
281             }
282             else {
283 0           die $@;
284             }
285             }
286            
287 0           return 1;
288             }
289              
290             =head2 $db->get_dbh()
291              
292             This method is mainly for the DB dependent modules to use, it returns the
293             underlying DBI database handle. There will probably have code added here
294             to check the db is still connected, so it may be wise to always use this
295             method rather than trying to retrieve $self->{dbh} directly.
296              
297             =cut
298              
299             sub get_dbh {
300             # maybe add code here to check connection status.
301             # or maybe add check once every 10 get_dbh's...
302 0     0 1   return shift->{dbh};
303             }
304              
305             sub DESTROY {
306 0     0     my $self = shift;
307 0           my $dbh;
308 0 0         if ($dbh = $self->get_dbh) {
309 0           $dbh->disconnect;
310             }
311             }
312              
313             =head2 Controlling error propagation from AUTOLOADed DBI methods
314              
315             Typicially the implementation packages will make calls to DBI methods
316             as though they were methods of the DBIx::AnyDBD object. If one of
317             these methods reports an error in DBI::AnyDBD then the error is caught
318             and rethrown by DBIx::AnyDBD so that the error is reported as occuring
319             in the implementation module. It does this by calling Carp::croak()
320             with the current package set to DBIx::AnyDBD::Carp.
321              
322             Usually this the the right thing to do but sometimes you may want to
323             report the error in the line containing the original method call on
324             the DBIx::AnyDBD object. In this case you should temporarily set
325             @DBIx::AnyDBD::Carp::ISA.
326              
327             my $db = DBIx::AnyDBD->connect("dbi:Oracle:sid1",
328             "user", "pass", {}, "MyClass");
329              
330             my $foo = $db->foo;
331             my $blee = $db->blee("too few arguments"); # Error reported here
332              
333             package MyClass::Oracle;
334            
335             sub foo {
336             shift->prepare("Invalid SQL"); # Error reported here
337             }
338              
339             sub blee {
340             local @DBIx::AnyDBD::Carp::ISA = __PACKAGE__;
341             shift->selectall_arrayref(BLEE_STATEMENT,{},@_); # Error not reported here
342             }
343              
344             =cut
345              
346             sub AUTOLOAD {
347 0     0     (my $func = $AUTOLOAD) =~ s/.*:://;
348 1     1   7 no strict 'refs';
  1         2  
  1         243  
349             # The following is much more elegant but doesn't work!
350             # *$func = sub {
351             # unshift @_ => shift->get_dbh;
352             # goto &{$_[0]->can($func)};
353             # };
354             *$func = sub {
355 0     0     my $dbh = shift->get_dbh;
356 0 0         if (wantarray) {
    0          
357 0           my @r = eval { $dbh->$func(@_) };
  0            
358 0 0         return @r unless $@;
359             } elsif (defined wantarray) {
360 0           my $r = eval { $dbh->$func(@_) };
  0            
361 0 0         return $r unless $@;
362             } else {
363 0           eval { $dbh->$func(@_) };
  0            
364 0 0         return unless $@;
365             };
366 0 0         if ( $@ =~ /(.*) at ${\__FILE__} .*$/ ) {
  0            
367             # We want croak() to report errors as occouring
368             # in the implementation class even though we are related
369             package DBIx::AnyDBD::Carp;
370 0           require Carp;
371 0           Carp::croak "$1";
372             }
373 0           die; # Unreachable ?
374 0           };
375 0           goto &$func;
376             }
377              
378             1;
379              
380             __END__