File Coverage

blib/lib/Apache2/Controller/DBI/Connector.pm
Criterion Covered Total %
statement 15 15 100.0
branch n/a
condition n/a
subroutine 5 5 100.0
pod n/a
total 20 20 100.0


line stmt bran cond sub pod time code
1             package Apache2::Controller::DBI::Connector;
2              
3             =head1 NAME
4              
5             Apache2::Controller::DBI::Connector -
6             connects L to C<< $r->pnotes->{a2c}{dbh} >>
7             or the key that you select.
8              
9             =head1 VERSION
10              
11             Version 1.001.001
12              
13             =cut
14              
15 1     1   1943 use version;
  1         1  
  1         6  
16             our $VERSION = version->new('1.001.001');
17              
18             =head1 SYNOPSIS
19              
20             =head2 USAGE
21              
22             sub some_a2c_controller_method {
23             my ($self, @path_args) = @_;
24             my $dbh = $self->pnotes->{a2c}{dbh};
25             }
26              
27             =head2 CONFIGURATION
28              
29             =head3 CONFIG ALTERNATIVE 1: APACHE CONF
30              
31             # virtualhost.conf:
32            
33             PerlLoadModule Apache::DBI
34             PerlLoadModule Apache2::Controller::Directives
35            
36             A2C_DBI_DSN DBI:mysql:database=foobar;host=localhost
37             A2C_DBI_User heebee
38             A2C_DBI_Password jeebee
39             A2C_DBI_Options RaiseError 1
40             A2C_DBI_Options AutoCommit 0
41              
42             # this boolean pushes a PerlLogHandler to run rollback if in_txn
43             A2C_DBI_Cleanup 1
44              
45             SetHandler modperl
46             PerlInitHandler MyApp::Dispatch
47             PerlHeaderParserHandler Apache2::Controller::DBI::Connector
48            
49              
50             =head3 CONFIG ALTERNATIVE 2: SUBCLASS
51              
52             If you need to make your life more complicated,
53             subclass this module and implement your own C<>
54             subroutine, which returns argument list for C<connect()>>.
55              
56             PerlLoadModule Apache::DBI
57            
58             SetHandler modperl
59              
60             PerlInitHandler MyApp::Dispatch
61             PerlHeaderParserHandler MyApp::DBIConnect
62            
63              
64             package MyApp::DBIConnect;
65             use base qw( Apache2::Controller::DBI::Connector );
66             sub dbi_connect_args {
67             my ($self) = @_;
68             return (
69             'DBI:mysql:database=foobar;host=localhost',
70             'heebee', 'jeebee',
71             { RaiseError => 1, AutoCommit => 0 }
72             );
73             }
74             sub dbi_cleanup { 1 }
75             sub dbi_pnotes_name { 'dbh' }
76              
77             1;
78              
79             You also have to use overloaded subs in a subclass if you want
80             to set up multiple DBH handles by specifying the name for the
81             key in pnotes using C<< A2C_DBI_PNOTES_NAME >> or C<< dbi_pnotes_name() >>.
82              
83             =head1 DESCRIPTION
84              
85             Connects a package-space L handle to C<< $r->pnotes->{a2c}{dbh} >>.
86              
87             You only need this where you need a database handle for every
88             request, for example to connect to a session database regardless of
89             whether the user does anything.
90              
91             You can load it only for certain locations, so the handle will get
92             connected only there.
93              
94             Otherwise you probably just want to use L and connect
95             your database handles on an ad-hoc basis from your controllers.
96              
97             If directive C<< A2C_DBI_Cleanup >> is set, a C<< PerlLogHandler >>
98             gets pushed which will roll back any open transactions. So if your
99             controller does some inserts and then screws up, you don't have to
100             worry about trapping these in eval if you want the DBI errors to
101             bubble up. They will be automatically rolled back since C<< commit() >>
102             was never called.
103              
104             (This used to be a PerlCleanupHandler, but it appears that Apache
105             hands this off to a thread even if running under prefork, and
106             cleanup doesn't always get processed before the child handles
107             the next request. At least, this is true under L.
108             Wacky. So, it's a PerlLogHandler to make sure the commit or
109             rollback gets done before the connection dies.)
110              
111             If you subclass, you can set up multiple dbh handles with different params:
112              
113            
114             SetHandler modperl
115              
116             PerlInitHandler MyApp::Dispatch
117             PerlHeaderParserHandler MyApp::DBI::Writer MyApp::DBI::Read
118            
119              
120             If you use a tiered database structure with one master record
121             and many replicated nodes, you can do it this way. Then you
122             overload C<< dbi_pnotes_name >> to provide the pnotes key,
123             say "dbh_write" and "dbh_read". In the controller get them
124             with C<< $self->pnotes->{a2c}{dbh_write} >> and
125             C<< $self->pnotes->{a2c}{dbh_read} >>, etc.
126              
127             If you subclass DBI, specify your DBI subclass name with
128             the directive C<< A2C_DBI_Class >>. Note that this has
129             to be connected using a string C<< eval() >> instead of
130             the block C<< eval() >> used for normal L if you
131             do not specify this directive.
132              
133             =head1 Accessing $dbh from controller
134              
135             In your L module for the URI, access the
136             database handle with C<< $self->pnotes->{a2c}{dbh} >>, or instead of
137             "dbh", whatever you set in directive C<< A2C_DBI_PNOTES_NAME >>
138             or return from your overloaded C<< dbi_pnotes_name() >> method.
139              
140             =head1 WARNING - DATABASE MEMORY USAGE
141              
142             Because a reference persists in package space, the database handle
143             will remain connected after a request ends.
144              
145             Usually Apache will rotate requests through child processes.
146              
147             This means that on a lightly-loaded server with a lot of spare child processes,
148             you will quickly get a large number of idle database connections, one per child.
149              
150             To solve this you need to set your database handle idle timeout
151             to some small number of seconds, say 5 or 10. Then you load
152             L in your Apache config file so they automatically
153             get reconnected if needed.
154              
155             Then when you get a load increase, handles are connected that persist
156             across requests long enough to handle the next request, but during
157             idle times, your database server conserves resources.
158              
159             There are various formulas for determining how much memory is
160             needed for the maximum number of connections your database server
161             provides. MySQL has a formula in their docs somewhere to calculate
162             memory needed for InnoDB handles. It is weird.
163              
164             When using
165             persistent database connections, it's a good idea to limit the
166             max number of Apache children to the max number of database connections
167             that your server can provide. Find a formula from your vendor's
168             documentation, if one exists, or wing it.
169              
170             =cut
171              
172 1     1   111 use strict;
  1         3  
  1         31  
173 1     1   6 use warnings FATAL => 'all';
  1         2  
  1         34  
174 1     1   5 use English '-no_match_vars';
  1         2  
  1         6  
175              
176 1         210 use base qw(
177             Apache2::Controller::NonResponseBase
178             Apache2::Controller::Methods
179 1     1   485 );
  1         2  
180              
181             use Log::Log4perl qw(:easy);
182             use YAML::Syck;
183              
184             use Apache2::Const -compile => qw( OK SERVER_ERROR );
185              
186             use Apache2::Controller::X;
187             #use Apache2::Controller::DBI;
188             use DBI;
189              
190             =head1 METHODS
191              
192             =head2 process
193              
194             Gets DBI connect arguments by calling C<< $self->dbi_connect_args() >>,
195             then connects C<< $dbh >> and stashes it in C<< $r->pnotes->{a2c}{dbh} >>
196             or the name you select.
197              
198             The $dbh has a reference in package space, so controllers using it
199             should always call commit or rollback. It's good practice to use
200             C<< eval >> anyway and throw an L or
201             your subclass of it (using C<< a2cx() >>,
202             so you can see the function path trace in the logs when the error occurs.
203              
204             The package-space $dbh for the child persists across requests, so
205             it is never destroyed. However, it is assigned with C<< DBI->connect() >>
206             on every request, so that L will cache the database handle and
207             actually connect it only if it cannot be pinged.
208              
209             =cut
210              
211             # the dbh is always connected, but there is only one instance of it.
212             my $dbh;
213             sub process {
214             my ($self) = @_;
215              
216             my $r = $self->{r};
217              
218             # connect the database:
219             my @args = $self->dbi_connect_args;
220             my $pnotes_name = $self->dbi_pnotes_name;
221              
222             a2cx "Already a dbh in pnotes->{$pnotes_name}"
223             if exists $r->pnotes->{a2c}{$pnotes_name};
224              
225             my $dbi_subclass = $self->get_directive('A2C_DBI_Class');
226              
227             if ($dbi_subclass) {
228             eval '$r->pnotes->{a2c}{'.$pnotes_name.'} = '.$dbi_subclass.'->connect(@args)';
229             }
230             else {
231             eval { $r->pnotes->{a2c}{$pnotes_name} = DBI->connect(@args) };
232             }
233             a2cx $EVAL_ERROR if $EVAL_ERROR;
234              
235             # push the log rollback handler if requested
236             if ($self->dbi_cleanup) {
237             # using a closure on '$pnotes_name' ... is this kosher?
238             # maybe this should push a class name of a separate cleanup class,
239             # which calls get_directives()?
240             # or, re-emulate getting the directive name? argh
241             $r->push_handlers(PerlLogHandler => sub {
242             my ($r) = @_;
243             my $dbh = $r->pnotes->{a2c}{$pnotes_name} || return Apache2::Const::OK;
244             if ($dbh->FETCH('BegunWork')) {
245             DEBUG("Cleanup handler: in txn. Rolling back...");
246             eval { $dbh->rollback() };
247             if ($EVAL_ERROR) {
248             my $error = "cleanup handler cannot roll back: $EVAL_ERROR";
249             ERROR($error);
250             $r->status_line(__PACKAGE__." $error");
251             return Apache2::Const::SERVER_ERROR;
252             }
253             else {
254             DEBUG("Cleanup handler rollback successful.");
255             }
256             }
257             else {
258             DEBUG("Cleanup handler not in txn.");
259             }
260             return Apache2::Const::OK;
261             });
262             }
263              
264             return;
265             }
266              
267             =head2 dbi_connect_args
268              
269             Default interprets directives. L.
270             You can override this in a subclass to provide your own connect args.
271              
272             =cut
273              
274             sub dbi_connect_args {
275             my ($self) = @_;
276             my $directives = $self->get_directives;
277             my @names = qw( DSN User Password Options );
278             my %opts = map {($_ => $directives->{"A2C_DBI_$_"})} @names;
279             return @opts{@names};
280             }
281              
282             =head2 dbi_cleanup
283              
284             Default interprets directive. L.
285             You can override this in a subclass.
286              
287             =cut
288              
289             sub dbi_cleanup { return shift->get_directive('A2C_DBI_Cleanup') }
290              
291             =head2 dbi_pnotes_name
292              
293             Maybe it would be useful to you to overload this.
294             But you'd probably better use the directive
295             L
296             in case other modules (like session) depend on it.
297              
298             =cut
299              
300             sub dbi_pnotes_name {
301             my ($self) = @_;
302             return $self->get_directive('A2C_DBI_Pnotes_Name') || 'dbh';
303             }
304              
305             =head1 SEE ALSO
306              
307             L
308              
309             L
310              
311             L
312              
313             L
314              
315             L
316              
317             =head1 AUTHOR
318              
319             Mark Hedges, C
320              
321             =head1 COPYRIGHT AND LICENSE
322              
323             Copyright 2008-2010 Mark Hedges. CPAN: markle
324              
325             This library is free software; you can redistribute it and/or modify
326             it under the same terms as Perl itself.
327              
328             This software is provided as-is, with no warranty
329             and no guarantee of fitness
330             for any particular purpose.
331              
332             =cut
333              
334             1;
335