File Coverage

blib/lib/HealthCheck/Diagnostic/DBHCheck.pm
Criterion Covered Total %
statement 77 80 96.2
branch 35 38 92.1
condition 17 27 62.9
subroutine 12 12 100.0
pod 3 3 100.0
total 144 160 90.0


line stmt bran cond sub pod time code
1             package HealthCheck::Diagnostic::DBHCheck;
2              
3             # ABSTRACT: Check a database handle to make sure you have read/write access
4 1     1   103445 use version;
  1         1820  
  1         6  
5             our $VERSION = 'v1.0.0'; # VERSION
6              
7 1     1   134 use 5.010;
  1         7  
8 1     1   6 use strict;
  1         2  
  1         17  
9 1     1   5 use warnings;
  1         2  
  1         25  
10 1     1   433 use parent 'HealthCheck::Diagnostic';
  1         272  
  1         5  
11              
12 1     1   2603 use Carp;
  1         9  
  1         54  
13 1     1   6 use Scalar::Util qw( blessed );
  1         3  
  1         968  
14              
15             sub new {
16 7     7 1 18519 my ($class, @params) = @_;
17              
18             # Allow either a hashref or even-sized list of params
19             my %params = @params == 1 && ( ref $params[0] || '' ) eq 'HASH'
20 7 50 33     47 ? %{ $params[0] } : @params;
  0         0  
21              
22             croak("The 'dbh' parameter should be a coderef!")
23 7 100 100     165 if ($params{dbh} && (ref $params{dbh} ne "CODE"));
24              
25 6         46 return $class->SUPER::new(
26             tags => [ 'dbh_check' ],
27             label => 'dbh_check',
28             %params
29             );
30             }
31              
32             sub check {
33 16     16 1 10989 my ( $self, %params ) = @_;
34              
35             # 1st, try to get dbh from provided parameters
36 16         39 my $dbh = $params{dbh};
37             # 2nd, if invoked with an object (not the class), then get dbh from object
38 16 100 66     80 $dbh ||= $self->{dbh} if ref $self;
39              
40 16 100       371 croak "Valid 'dbh' is required" unless $dbh;
41              
42 14 100       148 croak "The 'dbh' parameter should be a coderef!"
43             unless ref $dbh eq "CODE";
44              
45             my $db_access = $params{db_access} # Provided call to check()
46             // ((ref $self) && $self->{db_access}) # Value from new()
47 13   100     90 || "rw"; # default value
48              
49 13 100       154 croak "The value '$db_access' is not valid for the 'db_access' parameter"
50             unless $db_access =~ /^r[ow]$/;
51              
52             my $timeout =
53             defined $params{timeout} ? $params{timeout} :
54             (ref $self) && (defined $self->{timeout}) ? $self->{timeout} :
55 12 100 100     59 10;
    100          
56              
57 12         26 local $@;
58 12         23 eval {
59 12         56 local $SIG{__DIE__};
60 12     2   240 local $SIG{ALRM} = sub { die "timeout after $timeout seconds.\n" };
  2         3000631  
61 12         110 alarm $timeout;
62 12         60 $dbh = $dbh->(%params);
63             };
64 12         3308 alarm 0;
65              
66 12 100       93 if ( $@ =~ /^timeout/ ) {
67 2         15 chomp $@;
68             return {
69 2         61 status => 'CRITICAL',
70             info => "Database connection $@",
71             };
72             }
73              
74             # re-throw any other exceptions
75 10 100       29 die $@ if $@;
76              
77 8 100       248 croak "The 'dbh' coderef should return an object!"
78             unless blessed $dbh;
79              
80             my $db_class = $params{db_class} # Provided in call to check()
81             // ((ref $self) && $self->{db_class}) # Value from new
82 6   50     47 || "DBI::db"; # default value
83              
84 6         14 my $isa = ref $dbh;
85              
86 6 100       176 croak "The 'dbh' coderef should return a '$db_class', not a '$isa'"
87             unless $dbh->isa($db_class);
88              
89 5         30 my $res = $self->SUPER::check(
90             %params,
91             dbh => $dbh,
92             db_access => $db_access,
93             db_class => $db_class
94             );
95 5         367 delete $res->{dbh}; # don't include the object in the result
96              
97 5         24 return $res;
98             }
99              
100              
101             sub _read_write_temp_table {
102 2     2   11 my (%params) = @_;
103 2         13 my $dbh = $params{dbh};
104 2   50     10 my $table = $params{table_name} // "__DBH_CHECK__";
105 2         4 my $status = "CRITICAL";
106              
107 2         14 my $qtable = $dbh->quote_identifier($table);
108              
109             # Drop it like it's hot
110 2         1819 $dbh->do("DROP TEMPORARY TABLE IF EXISTS $qtable");
111              
112 2         80 $dbh->do(
113             join(
114             "",
115             "CREATE TEMPORARY TABLE IF NOT EXISTS $qtable (",
116             "check_id INTEGER PRIMARY KEY,",
117             "check_string VARCHAR(64) NOT NULL",
118             ")"
119             )
120             );
121              
122 2         632 $dbh->do(
123             join(
124             "",
125             "INSERT INTO $qtable ",
126             " (check_id, check_string) ",
127             "VALUES (1, 'Hello world')",
128             )
129             );
130 2         128 my @row = $dbh->selectrow_array(
131             "SELECT check_string FROM $qtable WHERE check_id = 1"
132             );
133              
134 2 50 33     211 $status = "OK" if ($row[0] && ($row[0] eq "Hello world"));
135              
136 2         18 $dbh->do("DROP TEMPORARY TABLE $qtable");
137              
138 2         60 return $status;
139             }
140              
141             sub run {
142 5     5 1 128 my ( $self, %params ) = @_;
143 5         11 my $dbh = $params{dbh};
144              
145             # Get db_access from parameters
146 5         21 my $read_write = ($params{db_access} =~ /^rw$/i);
147              
148 5         19 my $status = "OK";
149              
150             RUN_TESTS: {
151              
152             # See if we can ping the DB connection
153 5 100 66     13 if ($dbh->can("ping") && !$dbh->ping) {
  5         45  
154 2         29 $status = "CRITICAL";
155 2         5 last RUN_TESTS;
156             }
157              
158             # See if a simple SELECT works
159 3         63 my $value = eval { $dbh->selectrow_array("SELECT 1"); };
  3         24  
160 3 50 33     275 unless (defined $value && $value == 1) {
161 0         0 $status = "CRITICAL";
162 0         0 last RUN_TESTS;
163             }
164              
165 3 100       16 $status = _read_write_temp_table(%params) if $read_write;
166             }
167              
168             # Generate the human readable info string
169             my $info = sprintf(
170             "%s %s %s check of %s%s",
171             $status eq "OK" ? "Successful" : "Unsuccessful",
172             $dbh->{Driver}->{Name},
173             $read_write ? "read write" : "read only",
174             $dbh->{Name},
175 5 100       117 $dbh->{Username} ? " as $dbh->{Username}" : "",
    100          
    100          
176             );
177              
178 5         40 return { status => $status, info => $info };
179             }
180              
181             1;
182              
183             __END__