File Coverage

blib/lib/PGObject/Util/DBException.pm
Criterion Covered Total %
statement 24 27 88.8
branch 2 10 20.0
condition 1 2 50.0
subroutine 7 8 87.5
pod 4 4 100.0
total 38 51 74.5


line stmt bran cond sub pod time code
1             =head1 NAME
2              
3             PGObject::Util::DBException -- Database Exceptions for PGObject
4              
5             =cut
6              
7             package PGObject::Util::DBException;
8              
9             =head1 VERSION
10              
11             2.4.0
12              
13             =cut
14              
15             our $VERSION = '2.4.0';
16              
17             =head1 SYNOPSIS
18              
19             use PGObject::Util::DBException;
20              
21             $dbh->execute(@args) || die
22             PGObject::Util::DBException->new($dbh, $query, @args);
23              
24             # if you need something without dbh:
25              
26             die PGObject::Util::DBException->internal($state, $string, $query, $args);
27              
28             # if $dbh is undef, then we assume it is a connection error and ask DBI
29              
30             # in a handler you can check
31             try {
32             some_db_func();
33             } catch {
34             if ($_->isa('PGObject::Util::DBException')){
35             if ($_->{state} eq '23505') {
36             warn "Duplicate data detected.";
37             }
38             log($_->log_msg);
39             die $_;
40             }
41             else {
42             die $_;
43             }
44              
45             =cut
46              
47 7     7   131810 use strict;
  7         14  
  7         269  
48 7     7   36 use warnings;
  7         15  
  7         415  
49 7     7   810 use overload '""' => 'short_string';
  7         2236  
  7         55  
50 7     7   7692 use DBI;
  7         95128  
  7         3780  
51              
52             our $STRINGIFY_STACKTRACE = 1;
53              
54             =head1 DESCRIPTION
55              
56             Database errors occur sometimes for a variety of reasons, including bugs,
57             environmental, security, or user access problems, or a variety of other
58             reasons. For applications to appropriately handle database errors, it is often
59             necessary to be able to act on categories of errors, while if we log errors for
60             later analysis we want more information there. For debugging (or even logging)
61             we might even want to capture stack traces in order to try to understand where
62             errors came from. On the other hand, if we just want to display an error, we
63             want to get an appropriate error string back.
64              
65             This class provides both options. On one side, it provides data capture for
66             logging, introspection, and analysis. On the other it provides a short string
67             form for display purposes.
68              
69             This is optimized around database errors. It is not intended to be a general
70             exception class outside the database layer.
71              
72             If C is loaded we also capture a stack trace.
73              
74             =head2 Internal Error Codes
75              
76             In order to handle internal PGObject errors, we rely on the fact that no
77             current SQL subclasses contian the letter 'A' which we will use to mean
78             Application. We therefore take existing SQLState classes and use AXX
79             (currently only A01 is used currently) to handle these errors.
80              
81             =over
82              
83             =item 26A01
84              
85             Function not found. No function with the discovery criteria set was found.
86              
87             =item 42A01
88              
89             Function not unique. Multiple functions for the discovery criteria were
90             found.
91              
92             =back
93              
94             =head2 Stack Traces
95              
96             If C is loaded, we will capture stack traces starting at the
97             exception class call itself.
98              
99             In order to be unobtrusive, these are stringified by default. This is to avoid
100             problems of reference counting and lifecycle that can happen when capturing
101             tracing information, If you want to capture the whole stack trace without
102             stringification, then you can set the following variable to 0:
103             C. Naturally this is best
104             done using the C keyword.
105              
106             Note that non-stringified stacktraces are B weakened and this can cause
107             things like database handles to persist for longer than they ordinarily would.
108             For this reason, turning off stringification is best reserved for cases where
109             it is absolutely required.
110              
111             =head1 CONSTRUCTORS
112              
113             All constructors are called exclusively via C<$class->method> syntax.
114              
115             =head2 internal($state, $errstr, $query, $args);
116              
117             Used for internal application errors. Creates an exception of this type with
118             these attributes. This is useful for appication errors within the PGObject
119             framework.
120              
121             =cut
122              
123             sub internal ($$$$@) {
124 1     1 1 198115 my ($class, $state, $errstr, $query, @args) = @_;
125 1         12 my $self = {
126             state => $state,
127             errstr => $errstr,
128             query => $query,
129             args => \@args,
130             trace => undef,
131             };
132 1 50       87 if (scalar grep { $_ eq 'Devel/StackTrace.pm' } keys %INC){
  136         262  
133 0 0       0 $self->{trace} = $STRINGIFY_STACKTRACE ? Devel::StackTrace->new->as_string
134             : Devel::StackTrace->new;
135             }
136 1         25 bless $self, $class;
137             }
138              
139              
140             =head2 new($dbh, $query, @args)
141              
142             This creates a new exception object. The SQL State is taken from the C<$dbh>
143             database handle if it is defined, and the C module if it is not.
144              
145             =cut
146              
147             sub new ($$$@) {
148 0     0 1 0 my ($class, $dbh, $query, @args) = @_;
149 0 0       0 return $class->internal(
    0          
150             (defined $dbh ? $dbh->state : $DBI::state ),
151             (defined $dbh ? $dbh->errstr : $DBI::errstr ),
152             $query, @args
153             );
154             }
155              
156             =head1 Stringificatoin
157              
158             This module provides two methods for string representation. The first, for
159             human-focused error messages also overloads stringification generally. The
160             second is primarily intended for logging purposes.
161              
162             =head2 short_string
163              
164             The C method returns a short string of C for human
165             presentation.
166              
167             =cut
168              
169             sub short_string ($) {
170 3     3 1 2431 my $self = shift;
171 3         16 return "$self->{state}: $self->{errstr}";
172             }
173              
174             =head2 log_msg
175              
176             As its name suggests, C aimes to provide full infomation for logging
177             purposes.
178              
179             The format here is:
180              
181             STATE state, errstr
182             Query: query
183             Args: joun(',', @args)
184             Trace: Stacktrace
185              
186              
187             =cut
188              
189             sub log_msg ($) {
190 1     1 1 2 my $self = shift;
191 1   50     4 my $query = ( $self->{query} // 'None' );
192             my $string = join "\n",
193             "STATE $self->{state}, $self->{errstr}",
194             "Query: $query",
195 1         7 "Args: " . (join ',', @{$self->{args}}),
196 1 50       4 ($self->{trace} ? "Trace: $self->{trace}" : ());
197 1         3 return $string;
198             }
199              
200             1;