File Coverage

blib/lib/Fey/DBIManager/Source.pm
Criterion Covered Total %
statement 91 92 98.9
branch 25 30 83.3
condition 8 12 66.6
subroutine 20 21 95.2
pod 2 3 66.6
total 146 158 92.4


line stmt bran cond sub pod time code
1             package Fey::DBIManager::Source;
2             BEGIN {
3 2     2   22410 $Fey::DBIManager::Source::VERSION = '0.16';
4             }
5              
6 2     2   15 use strict;
  2         3  
  2         73  
7 2     2   10 use warnings;
  2         4  
  2         62  
8 2     2   838 use namespace::autoclean;
  2         26488  
  2         14  
9              
10 2     2   6587 use DBI;
  2         49174  
  2         375  
11 2     2   2372 use Fey::Exceptions qw( param_error );
  2         16325  
  2         134  
12              
13 2     2   758158 use Moose;
  2         5498909  
  2         18  
14 2     2   33664 use MooseX::SemiAffordanceAccessor;
  2         48457  
  2         18  
15 2     2   21026 use MooseX::StrictConstructor;
  2         54193  
  2         18  
16              
17             has 'name' => (
18             is => 'ro',
19             isa => 'Str',
20             default => 'default',
21             );
22              
23             has 'dbh' => (
24             is => 'rw',
25             isa => 'DBI::db',
26             reader => '_dbh',
27             writer => '_set_dbh',
28             clearer => '_unset_dbh',
29             predicate => '_has_dbh',
30             lazy_build => 1,
31             );
32              
33             after '_unset_dbh' => sub { $_[0]->_clear_allows_nested_transactions() };
34              
35             has 'dsn' => (
36             is => 'rw',
37             isa => 'Str',
38             predicate => '_has_dsn',
39             writer => '_set_dsn',
40             required => 1,
41             );
42              
43             has 'username' => (
44             is => 'ro',
45             isa => 'Str',
46             default => '',
47             );
48              
49             has 'password' => (
50             is => 'ro',
51             isa => 'Str',
52             default => '',
53             );
54              
55             has 'attributes' => (
56             is => 'rw',
57             isa => 'HashRef',
58             writer => '_set_attributes',
59             default => sub { {} },
60             );
61              
62             has 'post_connect' => (
63             is => 'ro',
64             isa => 'CodeRef',
65             );
66              
67             has 'auto_refresh' => (
68             is => 'ro',
69             isa => 'Bool',
70             default => 1,
71             );
72              
73             has 'allows_nested_transactions' => (
74             is => 'ro',
75             isa => 'Bool',
76             lazy_build => 1,
77             clearer => '_clear_allows_nested_transactions',
78             );
79              
80             has '_threaded' => (
81             is => 'ro',
82             isa => 'Bool',
83             lazy_build => 1,
84             init_arg => undef,
85             );
86              
87             has '_pid' => (
88             is => 'rw',
89             isa => 'Num',
90             init_arg => undef,
91             );
92              
93             has '_tid' => (
94             is => 'rw',
95             isa => 'Num',
96             init_arg => undef,
97             );
98              
99             has 'ping_interval' => (
100             is => 'ro',
101             isa => 'Maybe[Int]',
102             default => 60,
103             );
104              
105             has '_last_ping' => (
106             is => 'rw',
107             isa => 'Int',
108             default => 0,
109             clearer => '_clear_last_ping',
110             lazy => 1,
111             init_arg => undef,
112             );
113              
114             sub BUILD {
115 13     13 0 21 my $self = shift;
116 13         19 my $params = shift;
117              
118 13         507 $self->_set_attributes(
119             {
120 13         15 %{ $self->attributes() },
121             $self->_required_dbh_attributes(),
122             }
123             );
124              
125 13 100       522 if ( $self->_has_dbh() ) {
126 1         7 $self->_set_pid_tid();
127 1         5 $self->_apply_required_dbh_attributes();
128             }
129              
130 13         474 return $self;
131             }
132              
133             sub clone {
134 2     2 1 5 my $self = shift;
135              
136 12         390 my %p = map { $_ => $self->$_() }
  12         425  
137 2         6 grep { defined $self->$_() }
138             qw( dsn username password attributes post_connect auto_refresh );
139              
140 2         72 return ( ref $self )->new(
141             name => 'Clone of ' . $self->name(),
142             %p,
143             @_,
144             );
145             }
146              
147             sub _required_dbh_attributes {
148             return (
149 15     15   701 AutoCommit => 1,
150             RaiseError => 1,
151             PrintError => 0,
152             PrintWarn => 1,
153             ShowErrorStatement => 1,
154             );
155             }
156              
157             sub _apply_required_dbh_attributes {
158 1     1   3 my $self = shift;
159              
160 1         6 my %attr = $self->_required_dbh_attributes();
161              
162 1         11 for my $k ( sort keys %attr ) {
163 5         305 $self->dbh()->{$k} = $attr{$k};
164             }
165             }
166              
167             sub dbh {
168 27     27 1 6167 my $self = shift;
169              
170 27 100       1135 $self->_ensure_fresh_dbh() if $self->auto_refresh();
171              
172 27         1127 return $self->_dbh();
173             }
174              
175             sub _build_dbh {
176 12     12   22 my $self = shift;
177              
178 12         414 my $dbh = DBI->connect(
179             $self->dsn(), $self->username(),
180             $self->password(), $self->attributes()
181             );
182              
183 12         22369 $self->_set_pid_tid();
184              
185 12 100       457 if ( my $pc = $self->post_connect() ) {
186 9         27 $pc->($dbh);
187             }
188              
189 12         513 $self->_set_dbh($dbh);
190              
191 12         503 return $self->_dbh();
192             }
193              
194             sub _build_allows_nested_transactions {
195 3     3   6 my $self = shift;
196              
197 3         9 my $dbh = $self->dbh();
198              
199 3         7 my $allows_nested = eval {
200              
201             # This error comes from DBI in its default implementation
202             # of begin_work(). There didn't seem to be a way to shut
203             # this off (setting PrintWarn to false does not do it, and
204             # setting Warn to false does not stop it for all drivers,
205             # either). Hopefully the message text won't change.
206             #
207             # The variant is for DBD::Mock, which has a slightly
208             # different version of the text.
209             local $SIG{__WARN__} = sub {
210 0 0 0 0   0 warn @_
211             unless $_[0] =~ /Already (?:with)?in a transaction/i
212             || $_[0] =~ /rollback ineffective/;
213 3         30 };
214              
215 3         3920 $dbh->begin_work();
216 3         801 $dbh->begin_work();
217 1         13 $dbh->rollback();
218 1         8 $dbh->rollback();
219 1         34 1;
220             };
221              
222 3 100       148 if ($@) {
223 2 50       17 $dbh->rollback() unless $dbh->{AutoCommit};
224             }
225              
226 3         735 return $allows_nested;
227             }
228              
229             sub _build__threaded {
230 10 100   10   420 return threads->can('tid') ? 1 : 0;
231             }
232              
233             sub _set_pid_tid {
234 13     13   24 my $self = shift;
235              
236 13         571 $self->_set_pid($$);
237 13 50       510 $self->_set_tid( threads->tid() ) if $self->_threaded();
238             }
239              
240             # The logic in this method is largely borrowed from
241             # DBIx::Class::Storage::DBI.
242             sub _ensure_fresh_dbh {
243 27     27   10404 my $self = shift;
244              
245 27         1015 my $dbh = $self->_dbh();
246 27 100       3215 if ( $self->_pid() != $$ ) {
247 1         9 $dbh->{InactiveDestroy} = 1;
248 1         35 $self->_unset_dbh();
249 1         7 undef $dbh;
250             }
251              
252 27 100 66     1525 if ( $self->_threaded()
253             && $self->_tid() != threads->tid() ) {
254 1         8 $self->_unset_dbh();
255 1         6 undef $dbh;
256             }
257              
258 27 100 100     391 if ( $dbh && !( $dbh->{Active} && $self->_ping_dbh() ) ) {
      100        
259 2         26 $dbh->disconnect();
260 2         25 $self->_unset_dbh();
261             }
262              
263 27 100       1174 $self->_build_dbh() unless $self->_has_dbh();
264             }
265              
266             sub _ping_dbh {
267 24     24   346 my $self = shift;
268              
269 24         140 my $now = time();
270              
271 24 50       930 return 1 unless defined $self->ping_interval();
272 24 100       920 return 1 if ( $now - $self->_last_ping() ) < $self->ping_interval();
273              
274 13 100       466 if ( $self->_dbh()->ping() ) {
275 12         611 $self->_set_last_ping($now);
276 12         60 return 1;
277             }
278             else {
279 1         49 $self->_clear_last_ping();
280 1         7 return 0;
281             }
282             }
283              
284             __PACKAGE__->meta()->make_immutable();
285              
286             1;
287              
288             # ABSTRACT: Wraps a single DBI handle
289              
290              
291              
292             =pod
293              
294             =head1 NAME
295              
296             Fey::DBIManager::Source - Wraps a single DBI handle
297              
298             =head1 VERSION
299              
300             version 0.16
301              
302             =head1 SYNOPSIS
303              
304             my $source = Fey::DBIManager::Source->new( dbh => $dbh );
305              
306             my $dbh = $source->dbh();
307              
308             =head1 DESCRIPTION
309              
310             A C<Fey::DBIManager::Source> object provides a wrapper around a C<DBI>
311             handle which does things like ensure that handles are recreated
312             properly after a fork.
313              
314             A source can be created from an existing DBI handle, or from
315             parameters such as the dsn and authentication info.
316              
317             =head1 METHODS
318              
319             This class provides the following methods:
320              
321             =head2 Fey::DBIManager::Source->new(...)
322              
323             Creates a new C<Fey::DBIManager::Source> object. This method accepts a
324             number of parameters.
325              
326             =over 4
327              
328             =item * name
329              
330             The name of the source. This defaults to "default", which cooperates
331             nicely with L<Fey::DBIManager>.
332              
333             =item * dbh
334              
335             An already connected C<DBI> handle. Even if this is given, you still
336             need to provide the relevant connection parameters such as "dsn".
337              
338             =item * dsn
339              
340             A C<DBI> DSN string. This is required.
341              
342             =item * username
343              
344             =item * password
345              
346             The username and password for the source. These both default to an
347             empty string.
348              
349             =item * attributes
350              
351             A hash reference of attributes to be passed to C<< DBI->connect()
352             >>. Note that some attributes are set for all handles. See L<REQUIRED
353             ATTRIBUTES> for more details. This attribute is optional.
354              
355             =item * post_connect
356              
357             This is an optional subroutine reference which will be called after a
358             handle is created with C<< DBI->connect() >>. This is a handy way to
359             set connection info or to set driver-specific attributes like
360             "mysql_enable_utf8" or "pg_auto_escape".
361              
362             =item * auto_refresh
363              
364             A boolean value. The default is true, which means that whenever you
365             call C<< $source->dbh() >>, the source ensures that the database
366             handle is still active. See L<HANDLE FRESHNESS> for more details.
367              
368             =item * ping_interval
369              
370             An integer value representing the minimum number of seconds between
371             successive pings of the database handle. See L<HANDLE FRESHNESS> for
372             more details. The default value is 60 (seconds). A value of 0 causes
373             the source to ping the database handle each time you call
374             C<< $source->dbh() >>.
375              
376             If you explicitly set this value to C<undef>, then the database will never be
377             pinged.
378              
379             Note that if "auto_refresh" is false, this attribute is meaningless.
380              
381             =back
382              
383             =head2 $source->dbh()
384              
385             Returns a database handle for the source. If you did not pass a handle
386             to the constructor, this may create a new handle. If C<auto_refresh>
387             is true, this may cause a new handle to be created. See L<HANDLE
388             FRESHNESS> for more details.
389              
390             =head2 $source->dsn()
391              
392             =head2 $source->username()
393              
394             =head2 $source->password()
395              
396             =head2 $source->post_connect()
397              
398             =head2 $source->auto_connect()
399              
400             These methods simply return the value of the specified attribute.
401              
402             =head2 $source->attributes()
403              
404             This method returns attributes hash reference for the source. This
405             will be a combination of any attributes passed to the constructor plus
406             the L<REQUIRED ATTRIBUTES>.
407              
408             =head2 $source->allows_nested_transactions()
409              
410             Returns a boolean indicating whether or not the database to which the
411             source connects supports nested transactions. It does this by trying
412             to issue two calls to C<< $dbh->begin_work() >> followed by two calls
413             to C<< $dbh->rollback() >> (in an eval block).
414              
415             =head2 $source->clone(...)
416              
417             Returns a new source which is a clone of the original. If no name is provided,
418             it is created as "Clone of <original name>". The cloned source I<does not>
419             share the original's database handle.
420              
421             Any arguments passed to this method are passed to the constructor when
422             creating the clone.
423              
424             =head1 REQUIRED ATTRIBUTES
425              
426             In order to provide consistency for C<Fey::ORM>, sources enforce a set
427             of standard attributes on DBI handles:
428              
429             =over 4
430              
431             =item * AutoCommit => 1
432              
433             =item * RaiseError => 1
434              
435             =item * PrintError => 0
436              
437             =item * PrintWarn => 1
438              
439             =item * ShowErrorStatement => 1
440              
441             =back
442              
443             =head1 HANDLE FRESHNESS
444              
445             If C<auto_refresh> is true for a source, then every call to C<<
446             $source->dbh() >> incurs the cost of a "freshness" check. The upside
447             of this is that it will just work in the face of forks, threading, and
448             lost connections.
449              
450             First, we check to see if the pid has changed since the handle was created. If
451             it has, we set C<InactiveDestroy> to true in the handle before making a new
452             handle. If the thread has changed, we just make a new handle.
453              
454             Next, we check C<< $dbh->{Active] >> and, if this is false, we
455             disconnect the handle.
456              
457             Finally, we check that the handle has responded to C<< $dbh->ping() >>
458             within the past C<< $source->ping_interval() >> seconds. If it hasn't,
459             we call C<< $dbh->ping() >> and, if it returns false, we disconnect the
460             handle.
461              
462             If the handle is not fresh, a new one is created.
463              
464             =head1 BUGS
465              
466             Please report any bugs or feature requests to
467             C<bug-fey-dbimanager@rt.cpan.org>, or through the web interface at
468             L<http://rt.cpan.org>. I will be notified, and then you'll
469             automatically be notified of progress on your bug as I make changes.
470              
471             =head1 AUTHOR
472              
473             Dave Rolsky <autarch@urth.org>
474              
475             =head1 COPYRIGHT AND LICENSE
476              
477             This software is Copyright (c) 2011 by Dave Rolsky.
478              
479             This is free software, licensed under:
480              
481             The Artistic License 2.0 (GPL Compatible)
482              
483             =cut
484              
485              
486             __END__
487