File Coverage

blib/lib/DBIx/SQLstate.pm
Criterion Covered Total %
statement 83 83 100.0
branch 17 22 77.2
condition 2 3 66.6
subroutine 26 26 100.0
pod 13 22 59.0
total 141 156 90.3


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