File Coverage

blib/lib/Ima/DBI/Contextual.pm
Criterion Covered Total %
statement 66 82 80.4
branch 12 22 54.5
condition n/a
subroutine 13 15 86.6
pod 0 3 0.0
total 91 122 74.5


line stmt bran cond sub pod time code
1              
2             package Ima::DBI::Contextual;
3              
4 3     3   2343 use strict;
  3         5  
  3         110  
5 3     3   17 use warnings 'all';
  3         4  
  3         94  
6 3     3   15 use Carp 'confess';
  3         4  
  3         199  
7 3     3   7043 use DBI;
  3         83978  
  3         234  
8 3     3   39 use Digest::MD5 'md5_hex';
  3         4  
  3         196  
9 3     3   13088 use Time::HiRes 'usleep';
  3         7161  
  3         16  
10              
11             our $VERSION = '1.006';
12              
13             my $cache = { };
14            
15             sub set_db
16             {
17 1     1 0 22 my ($pkg) = shift;
18 1 50       4 $pkg = ref($pkg) ? ref($pkg) : $pkg;
19 1         3 my ($name) = shift;
20 1         3 my @dsn_with_attrs = @_;
21 1         3 my @dsn = grep { ! ref($_) } @_;
  3         8  
22 1         1 my ($attrs) = grep { ref($_) } @_;
  3         6  
23 1         8 my $default_attrs = {
24             RaiseError => 1,
25             AutoCommit => 0,
26             PrintError => 0,
27             Taint => 1,
28             AutoInactiveDestroy => 1,
29             };
30 1 50       4 map { $attrs->{$_} = $default_attrs->{$_} unless defined($attrs->{$_}) }
  5         26  
31             keys %$default_attrs;
32            
33 1         6 @dsn_with_attrs = ( @dsn, $attrs );
34              
35 3     3   1536 no strict 'refs';
  3         7  
  3         111  
36 3     3   18 no warnings 'redefine';
  3         6  
  3         2918  
37 1         9 *{"$pkg\::db_$name"} = $pkg->_mk_closure( $name, \@dsn, $attrs );
  1         12  
38 1         6 return;
39             }# end set_db()
40              
41              
42             sub _mk_closure
43             {
44 1     1   3 my ($pkg, $name, $dsn, $attrs) = @_;
45            
46             return sub {
47 3     3   920 my ($class) = @_;
48            
49 3         11 my @dsn = @$dsn;
50            
51 3         12 $attrs->{pid} = $$;
52 3         23 my $key = $class->_context( $name, \@dsn, $attrs );
53 3         6 my $dbh;
54 3 100       13 if( $dbh = $cache->{$key}->{dbh} )
55             {
56 2 50       13 if( $class->_ping($dbh) )
57             {
58             # dbh belongs to this process and it's good:
59             # YAY:
60             }
61             else
62             {
63             # dbh has gone stale. reconnect:
64 0         0 my $child_attrs = { %$attrs };
65 0         0 my $clone = $dbh->clone($child_attrs);
66 0         0 $dbh->{InactiveDestroy} = 1;
67 0         0 undef($dbh);
68            
69             # Now - make sure that the clone worked:
70 0 0       0 if( $class->_ping( $clone ) )
71             {
72             # This is a good clone - use it:
73 0         0 $dbh = $cache->{$key}->{dbh} = $clone;
74             }
75             else
76             {
77             # The clone was no good - reconnect:
78 0         0 $dbh = $cache->{$key}->{dbh} = DBI->connect_cached(@dsn, $attrs);
79             }# end if()
80             }# end if()
81             }
82             else
83             {
84             # We have not connected yet - engage:
85 1         10 $dbh = $cache->{$key}->{dbh} = DBI->connect_cached(@dsn, $attrs);
86             }# end if()
87            
88             # Finally:
89 3         15033 return $dbh;
90 1         9 };
91             }# end _mk_closure()
92              
93              
94             sub _context
95             {
96 3     3   15 my ($class, $name, $dsn, $attrs) = @_;
97            
98 3         8 my @parts = ($name );
99 3         10 $attrs->{child_pid} = $$;
100 3 50       13 eval { push @parts, threads->tid }
  0         0  
101             if $INC{'threads.pm'};
102 3         8 foreach( $dsn, $attrs )
103             {
104 6 100       21 if( ref($_) eq 'HASH' )
    50          
105             {
106 3         5 my $h = $_;
107 3         23 push @parts, map {"$_=$h->{$_}"} sort keys %$h;
  21         68  
108             }
109             elsif( ref($_) eq 'ARRAY' )
110             {
111 3         8 push @parts, @$_;
112             }
113             else
114             {
115 0         0 push @parts, $_;
116             }# end if()
117             }# end foreach()
118            
119 3         63 return md5_hex(join ", ", @parts);
120             }# end _context()
121              
122              
123             sub _ping
124             {
125 2     2   3 my ($class, $dbh) = @_;
126            
127             # Forgive the "If Slalom" - putting each condition on a separate line gives us
128             # better error messages were one of them to fail:
129 2 50       10 if( $dbh )
130             {
131 2 50       31 if( $dbh->FETCH('Active') )
132             {
133 2 50       12 if( $dbh->ping )
134             {
135 2         113 return $dbh;
136             }# end if()
137             }# end if()
138             }# end if()
139            
140            
141 0           return;
142             }# end _ping()
143              
144              
145             sub rollback
146             {
147 0     0 0   my ($class) = @_;
148 0           confess 'Deprecated';
149 0           $class->db_Main->rollback;
150             }# end dbi_rollback()
151              
152              
153             sub commit
154             {
155 0     0 0   my ($class) = @_;
156 0           confess 'Deprecated';
157 0           $class->db_Main->commit;
158             }# end dbi_commit()
159              
160             1;# return true:
161              
162             =pod
163              
164             =head1 NAME
165              
166             Ima::DBI::Contextual - Liteweight context-aware dbi handle cache and utility methods.
167              
168             =head1 DEPRECATED
169              
170             This module has been deprecated. Do not use.
171              
172             =head1 SYNOPSIS
173              
174             package Foo;
175            
176             use base 'Ima::DBI::Contextual';
177            
178             my @dsn = ( 'DBI:mysql:dbname:hostname', 'username', 'password', {
179             RaiseError => 0,
180             });
181             __PACKAGE__->set_db('Main', @dsn);
182              
183             Then, elsewhere:
184              
185             my $dbh = Foo->db_Main;
186            
187             # Use $dbh like you normally would:
188             my $sth = $dbh->prepare( ... );
189              
190             =head1 DESCRIPTION
191              
192             If you like L but need it to be more context-aware (eg: tie dbi connections to
193             more than the name and process id) then you need C.
194              
195             =head1 RANT
196              
197             B: For permanent relief of symptoms related to hosting multiple mod_perl
198             web applications on one server, where each application uses a different database
199             but they all refer to the database handle via C<< Class->db_Main >>. Such symptoms
200             may include:
201              
202             =over 4
203              
204             =item * Wonky behavior which causes one website to fail because it's connected to the wrong database.
205              
206             Scenario - Everything is going fine, you're clicking around walking your client through
207             a demo of the web application and then BLAMMO - B<500 server error>! Another click and it's OK. WTF?
208             You look at the log for Foo application and it says something like "C"
209              
210             Funny thing is - you never connected to that database. You have no idea B it is trying to connect to that database.
211             Pouring over the guts in L it's clear that L only caches database
212             handles by Process ID (C<$$>) and name (eg: db_B
). So if the same Apache child
213             process has more than one application running within it and each application has C then
214             I.
215              
216             =item * Wondering for years what happened.
217              
218             Years, no less.
219              
220             =item * Not impressing your boss.
221              
222             Yeah - it can happen - when you have them take a look at your new shumwidget and
223             instead of working - it I work. All your preaching about unit tests and
224             DRY go right out the window when the basics (eg - connecting to the B) are broken.
225              
226             =back
227              
228             =head1 SEE ALSO
229              
230             L
231              
232             =head1 AUTHOR
233              
234             John Drago
235              
236             =head1 LICENSE
237              
238             This software is B software and may be used and redistributed under the same
239             terms as Perl itself.
240              
241             =cut
242