File Coverage

blib/lib/DBIx/SQLstate.pm
Criterion Covered Total %
statement 75 75 100.0
branch 11 16 68.7
condition 2 3 66.6
subroutine 22 22 100.0
pod 9 18 50.0
total 119 134 88.8


line stmt bran cond sub pod time code
1             package DBIx::SQLstate;
2              
3              
4              
5             =head1 NAME
6              
7             DBIx::SQLstate - message lookup and tokenization of SQL-State codes
8              
9             =head1 SYNOPSIS
10              
11             use DBI;
12             use DBIx::SQLstate;
13            
14             my $dbh = DBI->connect($data_source, $username, $password,
15             {
16             HandleError => sub {
17             my $msg = shift;
18             my $h = shift;
19            
20             my $state = $h->state;
21            
22             my $message = sprintf("%s - %s",
23             $state, DBIx::SQLstate->token($state)
24             );
25            
26             die $message;
27             }
28             }
29            
30             );
31              
32             =cut
33              
34              
35              
36 7     7   475967 use strict;
  7         82  
  7         213  
37 7     7   40 use warnings;
  7         21  
  7         419  
38              
39             our $VERSION = 'v0.0.4';
40              
41             our $DEFAULT_MESSAGE = 'Unknown SQL-state';
42             our $CONST_PREFIX ='SQLSTATE';
43              
44 7     7   41 use Exporter qw/import/;
  7         12  
  7         4577  
45              
46             our @EXPORT = (
47             );
48              
49             our @EXPORT_OK = (
50             'sqlstate_class_codes',
51             'sqlstate_class_const',
52             'sqlstate_class_message',
53             'sqlstate_class_token',
54             'sqlstate_codes',
55             'sqlstate_const',
56             'sqlstate_default_const',
57             'sqlstate_default_message',
58             'sqlstate_default_token',
59             'sqlstate_message',
60             'sqlstate_token',
61             );
62              
63             our %EXPORT_TAGS = (
64             message => [
65             'sqlstate_message',
66             'sqlstate_class_message',
67             'sqlstate_default_message',
68             ],
69             token => [
70             'sqlstate_token',
71             'sqlstate_class_token',
72             'sqlstate_default_token',
73             ],
74             const => [
75             'sqlstate_const',
76             'sqlstate_class_const',
77             'sqlstate_default_const',
78             ],
79             );
80              
81              
82             # message
83             #
84             # a class method that returns a human readable for a given SQL-state code
85             #
86             # This will fall through from the a subclass message to a class message and at
87             # last the default. The 'message' routines use `undef` if there is no associated
88             # message found.
89             #
90             sub message ($) {
91 18     18 1 114 my $class = shift;
92 18         31 my $sqlstate = shift;
93            
94 18         41 for (
95             sqlstate_message($sqlstate),
96             sqlstate_class_message($sqlstate),
97             sqlstate_default_message(),
98 21 100       89 ) { return $_ if defined $_ }
99             ;
100             }
101              
102             # token
103             #
104             # a class method that will return the tokenized version of the above `message`
105             # method.
106             #
107             sub token ($) {
108 10     10 1 109 my $class = shift;
109 10         17 my $sqlstate = shift;
110            
111 10         25 my $message = $class->message($sqlstate);
112            
113 10         28 return tokenize($message);
114             }
115              
116             # const
117             #
118             # a class method that will return the constant version of the above `message`
119             # method.
120             #
121             sub const ($) {
122 5     5 0 12 my $class = shift;
123 5         8 my $sqlstate = shift;
124            
125 5         13 my $message = $class->message($sqlstate);
126            
127 5         17 return constantize($message);
128             }
129              
130              
131              
132             my %SQLstate = ();
133              
134              
135              
136             # sqlstate_message
137             #
138             # returns the human readable message for a known SQL-state
139             # or
140             # returns undef in all other cases (missing arg or non existent)
141             #
142             sub sqlstate_message ($) {
143 1239 50   1239 1 2007 return unless defined $_[0];
144 1239         2353 return $SQLstate{$_[0]};
145             }
146              
147              
148              
149             # sqlstate_class_message
150             #
151             # returns a human readable message for any known SQL-state
152             # or
153             # returns undef in all other cases
154             #
155             # this is typically used when there is not a known SQL-state message
156             #
157             sub sqlstate_class_message ($) {
158 21 50   21 1 62 return unless defined $_[0];
159 21         81 return +{ sqlstate_class_codes() }->{sqlstate_class($_[0])};
160             }
161              
162              
163              
164             # sqlstate_default_message
165             #
166             # returns the default SQL-state message
167             #
168             sub sqlstate_default_message () {
169 21     21 1 60 return $DEFAULT_MESSAGE;
170             }
171              
172              
173              
174             # sqlstate_token
175             #
176             # returns a tokenized version of the sqlstate_message (or undef)
177             #
178             sub sqlstate_token ($) {
179 1     1 1 86 return tokenize( sqlstate_message(shift) );
180             }
181              
182              
183              
184             # sqlstate_class_token
185             #
186             # returns the tokenized version of sqlstate_class_message
187             #
188             sub sqlstate_class_token ($) {
189 1     1 1 5 return tokenize( sqlstate_class_message(shift) );
190             }
191              
192              
193              
194             # sqlstate_default_token
195             #
196             # returns the tokenized version of sqlstate_default_message
197             #
198             sub sqlstate_default_token () {
199 1     1 1 4 return tokenize( sqlstate_default_message() );
200             }
201              
202              
203              
204             # sqlstate_const
205             #
206             # returns the constant version of sqlstate_message
207             #
208             sub sqlstate_const ($) {
209 1     1 0 83 return constantize( sqlstate_message(shift) );
210             }
211              
212              
213             # sqlstate_class_const
214             #
215             # returns the constant version of sqlstate_class_message
216             #
217             sub sqlstate_class_const ($) {
218 1     1 0 9 return constantize( sqlstate_class_message(shift) );
219             }
220              
221              
222              
223             # sqlstate_default_const
224             #
225             # returns the constant version of sqlstate_default_message
226             #
227             sub sqlstate_default_const () {
228 1     1 0 4 return constantize( sqlstate_default_message() );
229             }
230              
231              
232              
233             # sqlstate_class
234             #
235             # returns the 2-byte code from a given 5-byte SQL-state
236             #
237             sub sqlstate_class ($) {
238 1239 50   1239 1 1961 return unless defined $_[0];
239 1239         2345 return substr($_[0],0,2);
240             }
241              
242              
243              
244             # sqlstate_codes
245             #
246             # returns a list of key=value pairs of 'registered' SQL-states codes
247             #
248             sub sqlstate_codes () {
249 30     30 0 25000 return %SQLstate;
250             }
251              
252              
253             # sqlstate_known_codes
254             #
255             # returns the list of key/value pairs of all known SQL-state codes
256             #
257             sub sqlstate_known_codes () {
258 7     7   7939 use DBIx::SQLstate::wikipedia;
  7         32  
  7         4300  
259            
260             return (
261 7     7 0 807 %DBIx::SQLstate::wikipedia::SQLstate,
262             );
263             }
264              
265              
266              
267             # sqlstate_class_codes
268             #
269             # returns a list of key/value pairs for 'registered' SQL-state classes
270             #
271             # that is, the keys are the 2-byte values of the SQL-states that end in '000'
272             #
273             sub sqlstate_class_codes () {
274             my %sqlstate_class_codes = map {
275 1218         1644 sqlstate_class($_) => sqlstate_message($_)
276 21     21 0 37 } grep { /..000/ } keys %{{ sqlstate_codes() }};
  6153         10406  
  21         44  
277            
278 21         1236 return %sqlstate_class_codes;
279             }
280              
281              
282              
283             # tokenize
284             #
285             # turns any given string into a kind of CamelCase string
286             #
287             # removing non alpha-numeric characters, preserving or correcting capitalisation
288             #
289             sub tokenize ($) {
290 13 50   13 0 43 return if !defined $_[0];
291            
292 13         20 my $text = shift;
293            
294             # remove rubish first
295 13         36 $text =~ s/,/ /ig;
296 13         37 $text =~ s/-/ /ig;
297 13         25 $text =~ s/_/ /ig;
298 13         24 $text =~ s/\//_/ig;
299            
300             # create special cases
301 13         39 $text =~ s/sql /sql_/ig;
302 13         25 $text =~ s/xml /xml_/ig;
303 13         25 $text =~ s/cli /cli_/ig;
304 13         38 $text =~ s/fdw /fdw_/ig;
305 13         25 $text =~ s/null /null_/ig;
306            
307            
308 13         43 $text = join qq(_), map { lc } split /_/, $text;
  20         69  
309 13 100 66     88 $text = join qq(), map { ucfirst(lc($_)) } grep { $_ ne 'a' and $_ ne 'an' and $_ ne 'the' } split /\s+/, $text;
  49         141  
  50         227  
310            
311             # fix special cases
312 13         43 $text =~ s/sql_/SQL/ig;
313 13         23 $text =~ s/xml_/XML/ig;
314 13         25 $text =~ s/cli_/CLI/ig;
315 13         20 $text =~ s/fdw_/FDW/ig;
316 13         24 $text =~ s/null_/NULL/ig;
317 13         22 $text =~ s/xquery/XQuery/ig;
318              
319 13         70 return $text;
320             }
321              
322              
323              
324             # constantize
325             #
326             # returns a uppercase snake-case version of the string
327             #
328             sub constantize ($) {
329 8 50   8 0 28 return if !defined $_[0];
330            
331 8         15 my $text = shift;
332            
333             # remove common words
334 8         55 $text =~ s/\b(?:a|an|the)\b//ig;
335            
336             # substitute anything not an alpha-numeric
337 8         49 $text =~ s/[^\d\w]+/_/ig;
338            
339             # trim leading or trailing underscores
340 8         44 $text =~ s/^_|_$//ig;
341            
342 8         21 $text = uc($text);
343 8 100       37 $text = join '_', $CONST_PREFIX, $text
344             if defined $CONST_PREFIX;
345            
346 8         44 return $text;
347             }
348              
349              
350              
351             %SQLstate = sqlstate_known_codes();
352              
353              
354              
355             =head1 DESCRIPTION
356              
357             Database Management Systems, and L have their own way of reporting errors.
358             Very often, errors are quit expressive in what happened. Many SQL based systems
359             do also include a SQL-State with each request. This module turns the SQL-State 5
360             byte code into human readable strings.
361              
362             =head1 SQLSTATE Classes and Sub-Classes
363              
364             Programs calling a database which accords to the SQL standard receive an
365             indication about the success or failure of the call. This return code - which is
366             called SQLSTATE - consists of 5 bytes. They are divided into two parts: the
367             first and second bytes contain a class and the following three a subclass. Each
368             class belongs to one of four categories: "S" denotes "Success" (class 00), "W"
369             denotes "Warning" (class 01), "N" denotes "No data" (class 02) and "X" denotes
370             "Exception" (all other classes).
371              
372             =cut
373              
374              
375              
376             =head1 CLASS METHODS
377              
378             The following two class methods have been added for the programmer convenience:
379              
380             =head2 C
381              
382             Returns a subclass-message or class-message for a given and exisitng SQLstate,
383             or the default C<'Unkown SQL-state'>.
384              
385             my $message = DBIx::SQLstate->message("25006");
386             #
387             # "read-only SQL-transaction"
388              
389             =head2 C
390              
391             Returns the tokenized (See L) version of the message from above.
392              
393             $sqlstate = "22XXX"; # non existing code
394             $LOG->error(DBIx::SQLstate->token $sqlstate)
395             #
396             # logs an error with "DataException"
397              
398             =cut
399              
400              
401              
402             =head1 EXPORT_OK SUBROUTINES
403              
404             =head2 C
405              
406             Returns the human readable message defined for the given SQL-State code.
407              
408             my $sqlstate = '25006';
409             say sqlstate_message();
410             #
411             # prints "read-only SQL-transaction"
412              
413              
414              
415             =head2 C
416              
417             Returns the human readable message for the SQL-state class. This might be useful
418             reduce the amount of variations of log-messages. But since not all SQLstate
419             codes might be present in the current table, this will provide a decent fallback
420             message.
421              
422             my $sqlstate = '22X00'; # a madeup code
423             my $m = sqlstate_message($sqlstate) // sqlstate_class_message($sqlstate);
424             say $m;
425             #
426             # prints "data exception"
427              
428              
429              
430             =head2 C
431              
432             Returns a default message. The value can be set with
433             C, and defaults to C<'Unkown SQL-state'>.
434              
435              
436              
437             =head2 C
438              
439             Returns a tokenized string (See L).
440              
441             my $sqlstate = '01007';
442             $LOG->warn sqlstate_token($sqlstate);
443             #
444             # logs a warning message with "PrivilegeNotGranted"
445              
446              
447              
448             =head2 C
449              
450             Returns the tokenized string for the above L. See
451             L.
452              
453              
454              
455              
456             =head2 C
457              
458             Returns the tokenized version of the default message.
459              
460              
461              
462             =head2 C
463              
464             Returns the 2-byte SQL-state class code.
465              
466              
467              
468             =head1 Tokenization
469              
470             The tokenized strings can be useful in logging, or for L ( or
471             L) object creations etc. These are mostly camel-case. However,
472             for some common abreviations, like 'SQL', 'XML' or 'XQuery' this module tries to
473             correct the charactercase-folding.
474              
475             For now, do not rely on the consitent case-folding, it may change in the future.
476              
477              
478              
479             =head1 AUTHOR
480              
481             Theo van Hoesel
482              
483              
484              
485             =head1 COPYRIGHT AND LICENSE
486              
487             'DBIx::SQLstate'
488             is Copyright (C) 2023, Perceptyx Inc
489              
490             This library is free software; you can redistribute it and/or modify it under
491             the terms of the Artistic License 2.0.
492              
493             This package is distributed in the hope that it will be useful, but it is
494             provided "as is" and without any express or implied warranties.
495              
496             For details, see the full text of the license in the file LICENSE.
497              
498              
499             =cut
500              
501              
502              
503             1;
504              
505              
506              
507             __END__