File Coverage

blib/lib/DBIx/ParseError/MySQL.pm
Criterion Covered Total %
statement 35 35 100.0
branch 10 10 100.0
condition n/a
subroutine 10 10 100.0
pod n/a
total 55 55 100.0


line stmt bran cond sub pod time code
1             package DBIx::ParseError::MySQL;
2              
3 1     1   197084 use utf8;
  1         7  
  1         6  
4 1     1   30 use strict;
  1         2  
  1         18  
5 1     1   5 use warnings;
  1         1  
  1         26  
6              
7 1     1   540 use Moo;
  1         12026  
  1         5  
8              
9 1     1   1551 use Scalar::Util qw( blessed );
  1         2  
  1         50  
10 1     1   646 use Types::Standard qw( Str Bool Object );
  1         76253  
  1         10  
11              
12             # ABSTRACT: Error parser for MySQL
13 1     1   1497 use version;
  1         1953  
  1         6  
14             our $VERSION = 'v1.0.1'; # VERSION
15              
16             #pod =head1 SYNOPSIS
17             #pod
18             #pod use DBIx::ParseError::MySQL;
19             #pod
20             #pod eval {
21             #pod my $result = $dbh->do('SELECT 1');
22             #pod };
23             #pod if ($@) {
24             #pod if (DBIx::ParseError::MySQL->new($@)->is_transient) { $dbh->reconnect }
25             #pod else { die; }
26             #pod }
27             #pod
28             #pod =head1 DESCRIPTION
29             #pod
30             #pod This module is a database error categorizer, specifically for MySQL. This module is also
31             #pod compatible with Galera's WSREP errors.
32             #pod
33             #pod =head1 ATTRIBUTES
34             #pod
35             #pod =head1 orig_error
36             #pod
37             #pod Returns the original, untouched error object or string.
38             #pod
39             #pod =cut
40              
41             has orig_error => (
42             is => 'ro',
43             isa => Str|Object,
44             required => 1,
45             );
46              
47             #pod =head1 error_string
48             #pod
49             #pod Returns the stringified version of the error.
50             #pod
51             #pod =cut
52              
53             has error_string => (
54             is => 'lazy',
55             isa => Str,
56             init_arg => undef,
57             );
58              
59             sub _build_error_string {
60 19     19   173 my $self = shift;
61              
62             # All of the exception objects should support this, too.
63 19         345 return $self->orig_error."";
64             }
65              
66             #pod =head1 error_type
67             #pod
68             #pod Returns a string that describes the type of error. These can be one of the following:
69             #pod
70             #pod lock Lock errors, like a lock wait timeout or deadlock
71             #pod connection Connection/packet failures, disconnections
72             #pod shutdown Errors that happen when a server is shutting down
73             #pod duplicate_value Duplicate entry errors
74             #pod unknown Any other error
75             #pod
76             #pod =cut
77              
78             has error_type => (
79             is => 'lazy',
80             isa => Str,
81             init_arg => undef,
82             );
83              
84             sub _build_error_type {
85 19     19   1582 my $self = shift;
86              
87 19         337 my $error = $self->error_string;
88              
89             # We have to capture just the first error, not other errors that may be buried in the
90             # stack trace.
91 19         590 $error =~ s/ at [^\n]+ line \d+\.?\n.+//s;
92              
93             # Disable /x flag to allow for whitespace within string, but turn it on for newlines
94             # and comments.
95             #
96             # These error messages are purposely long and case-sensitive, because we're looking
97             # for these errors -anywhere- in the string. Best to get as exact of a match as
98             # possible.
99              
100             # Locks
101 19 100       214 return 'lock' if $error =~ m<
102             (?-x:Deadlock found when trying to get lock; try restarting transaction)|
103             (?-x:Lock wait timeout exceeded; try restarting transaction)|
104             (?-x:WSREP detected deadlock/conflict and aborted the transaction.\s+Try restarting the transaction)
105             >x;
106              
107             # Various connection/packet problems
108 16 100       250 return 'connection' if $error =~ m<
109             # Connection dropped/interrupted
110             (?-x:MySQL server has gone away)|
111             (?-x:Lost connection to MySQL server)|
112             (?-x:Query execution was interrupted)|
113              
114             # Initial connection failure
115             (?-x:Bad handshake)|
116             (?-x:Too many connections)|
117             (?-x:Host '\S+' is blocked because of many connection errors)|
118             (?-x:Can't get hostname for your address)|
119             (?-x:Can't connect to (?:local )?MySQL server)|
120              
121             # Packet corruption
122             (?-x:Got a read error from the connection pipe)|
123             (?-x:Got (?:an error|timeout) (?:reading|writing) communication packets)|
124             (?-x:Malformed communication packet)
125             >x;
126              
127             # Failover/shutdown of node/server
128 7 100       70 return 'shutdown' if $error =~ m<
129             (?-x:WSREP has not yet prepared node for application use)|
130             (?-x:Server shutdown in progress)|
131             (?-x:Normal shutdown)|
132             (?-x:Shutdown complete)
133             >x;
134              
135             # Duplicate entry error
136 5 100       55 return 'duplicate_value' if $error =~ m<
137             # Any value can be in the first piece here...
138             (?-x:Duplicate entry '.+?' for key '\S+')
139             >xs; # include \n in .+
140              
141 3         52 return 'unknown';
142             }
143              
144              
145             #pod =head2 is_transient
146             #pod
147             #pod Returns a true value if the error is the type that is likely transient. For example,
148             #pod errors that recommend retrying transactions or connection failures. This check can be
149             #pod used to figure out if it's worth retrying a transaction.
150             #pod
151             #pod This is merely a check for the following L:
152             #pod C<< lock connection shutdown >>.
153             #pod
154             #pod =cut
155              
156             has is_transient => (
157             is => 'lazy',
158             isa => Bool,
159             init_arg => undef,
160             );
161              
162             sub _build_is_transient {
163 19     19   16940 my $self = shift;
164              
165 19         313 my $type = $self->error_type;
166              
167 19 100       396 return 1 if $type =~ /^(lock|connection|shutdown)$/;
168 5         80 return 0;
169             }
170              
171             #pod =head1 CONSTRUCTORS
172             #pod
173             #pod =head1 new
174             #pod
175             #pod my $parsed_error = DBIx::ParseError::MySQL->new($@);
176             #pod
177             #pod Returns a C object. Since the error is the only parameter, it
178             #pod can be passed by itself.
179             #pod
180             #pod =cut
181              
182             around BUILDARGS => sub {
183             my ($orig, $class, @args) = @_;
184              
185             if (@args == 1 && defined $args[0] && (!ref $args[0] || blessed $args[0])) {
186             my $error = shift @args;
187             push @args, ( orig_error => $error );
188             }
189              
190             return $class->$orig(@args);
191             };
192              
193             #pod =head1 SEE ALSO
194             #pod
195             #pod L - A similar parser, but specifically tailored to L.
196             #pod
197             #pod =cut
198              
199             1;
200              
201             __END__