File Coverage

blib/lib/Prancer/Plugin/Database/Driver.pm
Criterion Covered Total %
statement 61 94 64.8
branch 8 30 26.6
condition 8 23 34.7
subroutine 11 17 64.7
pod 0 2 0.0
total 88 166 53.0


line stmt bran cond sub pod time code
1             package Prancer::Plugin::Database::Driver;
2              
3 2     2   3933 use strict;
  2         12  
  2         76  
4 2     2   9 use warnings FATAL => 'all';
  2         4  
  2         68  
5              
6 2     2   7 use version;
  2         3  
  2         11  
7             our $VERSION = '1.02';
8              
9 2     2   157 use Try::Tiny;
  2         6  
  2         147  
10 2     2   14 use Carp;
  2         4  
  2         2113  
11              
12             # even though this *should* work automatically, it was not
13             our @CARP_NOT = qw(Prancer Try::Tiny);
14              
15             sub new {
16 7     7 0 11 my ($class, $config, $connection) = @_;
17              
18             try {
19 7     7   1612 require DBI;
20             } catch {
21 0 0   0   0 my $error = (defined($_) ? $_ : "unknown");
22 0         0 croak "could not initialize database connection '${connection}': could not load DBI: ${error}";
23 7         43 };
24              
25             # this is the only required field
26 7 50       13377 unless ($config->{'database'}) {
27 0         0 croak "could not initialize database connection '${connection}': no database name configured";
28             }
29              
30 7         33 my $self = bless({}, $class);
31 7         22 $self->{'_connection'} = $connection;
32 7         13 $self->{'_database'} = $config->{'database'};
33 7         12 $self->{'_username'} = $config->{'username'};
34 7         10 $self->{'_password'} = $config->{'password'};
35 7         10 $self->{'_hostname'} = $config->{'hostname'};
36 7         11 $self->{'_port'} = $config->{'port'};
37 7         11 $self->{'_autocommit'} = $config->{'autocommit'};
38 7         20 $self->{'_charset'} = $config->{'charset'};
39 7   50     20 $self->{'_check_threshold'} = $config->{'connection_check_threshold'} || 30;
40 7   50     32 $self->{'_dsn_extra'} = $config->{'dsn_extra'} || {};
41 7   50     26 $self->{'_on_connect'} = $config->{'on_connect'} || [];
42              
43             # store a pool of database connection handles
44 7         12 $self->{'_handles'} = {};
45              
46 7         26 return $self;
47             }
48              
49             sub handle {
50 12     12 0 14 my $self = shift;
51              
52             # to be fork safe and thread safe, use a combination of the PID and TID (if
53             # running with use threads) to make sure no two processes/threads share a
54             # handle. implementation based on DBIx::Connector by David E. Wheeler
55 12         21 my $pid_tid = $$;
56 12 50       27 $pid_tid .= "_" . threads->tid if $INC{'threads.pm'};
57              
58             # see if we have a matching handle
59 12   100     37 my $handle = $self->{'_handles'}->{$pid_tid} || undef;
60              
61 12 100       26 if ($handle->{'dbh'}) {
62 5 50 33     38 if ($handle->{'dbh'}{'Active'} && $self->{'_check_threshold'} &&
      33        
63             (time - $handle->{'last_connection_check'} < $self->{'_check_threshold'})) {
64              
65             # the handle has been checked recently so just return it
66 5         92 return $handle->{'dbh'};
67             } else {
68 0 0       0 if ($self->_check_connection($handle->{'dbh'})) {
69 0         0 $handle->{'last_connection_check'} = time;
70 0         0 return $handle->{'dbh'};
71             } else {
72             # er need to reconnect
73 0         0 carp "database connection to '${\$self->{'_connection'}}' went away -- reconnecting";
  0         0  
74              
75             # try to disconnect but don't care if it fails
76 0 0       0 if ($handle->{'dbh'}) {
77 0     0   0 try { $handle->{'dbh'}->disconnect(); } catch {};
  0         0  
  0         0  
78             }
79              
80             # try to connect again and save the new handle
81 0         0 $handle->{'dbh'} = $self->_get_connection();
82 0         0 return $handle->{'dbh'};
83             }
84             }
85             } else {
86 7         23 $handle->{'dbh'} = $self->_get_connection();
87 7 50       19 if ($handle->{'dbh'}) {
88 7         15 $handle->{'last_connection_check'} = time;
89 7         12 $self->{'_handles'}->{$pid_tid} = $handle;
90 7         22 return $handle->{'dbh'};
91             }
92             }
93              
94 0         0 return;
95             }
96              
97             sub _get_connection {
98 7     7   8 my $self = shift;
99              
100 7         12 my $dbh = undef;
101             try {
102 7   33 7   169 $dbh = DBI->connect(@{$self->{'_dsn'}}) || croak "${\$DBI::errstr}\n";
103              
104             # run any on_connect sql
105 7         2463 $dbh->do($_) for (@{$self->{'_on_connect'}});
  7         29  
106             } catch {
107 0 0   0   0 my $error = (defined($_) ? $_ : "unknown");
108 0         0 croak "could not initialize database connection '${\$self->{'_connection'}}': ${error}";
  0         0  
109 7         48 };
110              
111 7         99 return $dbh;
112             }
113              
114             # Check the connection is alive
115             sub _check_connection {
116 0     0   0 my $self = shift;
117 0         0 my $dbh = shift;
118 0 0       0 return 0 unless $dbh;
119              
120 0 0 0     0 if ($dbh->{Active} && (my $result = $dbh->ping())) {
121 0 0       0 if (int($result)) {
122             # DB driver itself claims all is OK, trust it:
123 0         0 return 1;
124             } else {
125             # it was "0 but true" meaning the DBD doesn't implement ping and
126             # instead we got the default DBI ping implementation. implement
127             # our own basic check by performing a real simple query.
128             return try {
129 0     0   0 return $dbh->do("SELECT 1");
130             } catch {
131 0     0   0 return 0;
132 0         0 };
133             }
134             }
135              
136 0         0 return 0;
137             }
138              
139             # stolen from Hash::Merge::Simple
140             ## no critic (ProhibitUnusedPrivateSubroutines)
141             sub _merge {
142 7     7   12 my ($self, $left, @right) = @_;
143              
144 7 50       21 return $left unless @right;
145 7 50       14 return $self->_merge($left, $self->_merge(@right)) if @right > 1;
146              
147 7         15 my ($right) = @right;
148 7         7 my %merged = %{$left};
  7         28  
149              
150 7         12 for my $key (keys %{$right}) {
  7         19  
151 0         0 my ($hr, $hl) = map { ref($_->{$key}) eq "HASH" } $right, $left;
  0         0  
152              
153 0 0 0     0 if ($hr and $hl) {
154 0         0 $merged{$key} = $self->_merge($left->{$key}, $right->{$key});
155             } else {
156 0         0 $merged{$key} = $right->{$key};
157             }
158             }
159              
160 7         18 return \%merged;
161             }
162              
163             1;