]> granicus.if.org Git - postgresql/commitdiff
Improve PL/Tcl errorCode facility by providing decoded name for SQLSTATE.
authorTom Lane <tgl@sss.pgh.pa.us>
Fri, 25 Mar 2016 20:54:52 +0000 (16:54 -0400)
committerTom Lane <tgl@sss.pgh.pa.us>
Fri, 25 Mar 2016 20:54:52 +0000 (16:54 -0400)
We don't really want to encourage people to write numeric SQLSTATEs in
programs; that's unreadable and error-prone.  Copy plpgsql's infrastructure
for converting between SQLSTATEs and exception names shown in Appendix A,
and modify examples in tests and documentation to do it that way.

doc/src/sgml/pltcl.sgml
src/backend/utils/errcodes.txt
src/pl/tcl/.gitignore
src/pl/tcl/Makefile
src/pl/tcl/expected/pltcl_setup.out
src/pl/tcl/generate-pltclerrcodes.pl [new file with mode: 0644]
src/pl/tcl/pltcl.c
src/pl/tcl/sql/pltcl_setup.sql
src/tools/msvc/Solution.pm

index 1ff9b96fa524fe9604a3843fb0622d960cd8d6a3..805cc89dc9913696c29ac08885bd602d7bb03afc 100644 (file)
@@ -813,14 +813,16 @@ CREATE EVENT TRIGGER tcl_a_snitch ON ddl_command_start EXECUTE PROCEDURE tclsnit
      word is <literal>POSTGRES</literal>, the second word is the Postgres
      version number, and additional words are field name/value pairs
      providing detailed information about the error.
-     Fields <varname>message</> and <varname>SQLSTATE</> (the error code
-     shown in <xref linkend="errcodes-appendix">) are always supplied.
+     Fields <varname>SQLSTATE</>, <varname>condition</>,
+     and <varname>message</> are always supplied
+     (the first two represent the error code and condition name as shown
+     in <xref linkend="errcodes-appendix">).
      Fields that may be present include
      <varname>detail</>, <varname>hint</>, <varname>context</>,
      <varname>schema</>, <varname>table</>, <varname>column</>,
      <varname>datatype</>, <varname>constraint</>,
      <varname>statement</>, <varname>cursor_position</>,
-     <varname>filename</>, <varname>lineno</> and
+     <varname>filename</>, <varname>lineno</>, and
      <varname>funcname</>.
     </para>
 
@@ -832,7 +834,7 @@ CREATE EVENT TRIGGER tcl_a_snitch ON ddl_command_start EXECUTE PROCEDURE tclsnit
 if {[catch { spi_exec $sql_command }]} {
     if {[lindex $::errorCode 0] == "POSTGRES"} {
         array set errorArray $::errorCode
-        if {$errorArray(SQLSTATE) == "42P01"} {  # UNDEFINED_TABLE
+        if {$errorArray(condition) == "undefined_table"} {
             # deal with missing table
         } else {
             # deal with some other type of SQL error
index 1a920e8bd2114df05a204f2c551d7e37c658a2c1..49494f9cd31510e3716145a0c0bedbf40592fc15 100644 (file)
@@ -15,6 +15,9 @@
 #   src/pl/plpgsql/src/plerrcodes.h
 #      a list of PL/pgSQL condition names and their SQLSTATE codes
 #
+#   src/pl/tcl/pltclerrcodes.h
+#      the same, for PL/Tcl
+#
 #   doc/src/sgml/errcodes-list.sgml
 #      a SGML table of error codes for inclusion in the documentation
 #
index 5dcb3ff9723501c3fe639bee1c1435e47a580a6f..62b62eb4595bd2ca981d5318760959eacbc6ede1 100644 (file)
@@ -1,3 +1,5 @@
+/pltclerrcodes.h
+
 # Generated subdirectories
 /log/
 /results/
index eb5c8a2de2453f22b17debe42d87232a1e831448..d77b7b95f241a3d9531f1c29a687ddb7da90f260 100644 (file)
@@ -13,7 +13,6 @@ include $(top_builddir)/src/Makefile.global
 
 override CPPFLAGS := $(TCL_INCLUDE_SPEC) $(CPPFLAGS)
 
-
 # On Windows, we don't link directly with the Tcl library; see below
 ifneq ($(PORTNAME), win32)
 SHLIB_LINK = $(TCL_LIB_SPEC) $(TCL_LIBS) -lc
@@ -56,6 +55,14 @@ include $(top_srcdir)/src/Makefile.shlib
 all: all-lib
        $(MAKE) -C modules $@
 
+# Force this dependency to be known even without dependency info built:
+pltcl.o: pltclerrcodes.h
+
+# generate pltclerrcodes.h from src/backend/utils/errcodes.txt
+pltclerrcodes.h: $(top_srcdir)/src/backend/utils/errcodes.txt generate-pltclerrcodes.pl
+       $(PERL) $(srcdir)/generate-pltclerrcodes.pl $< > $@
+
+distprep: pltclerrcodes.h
 
 install: all install-lib install-data
        $(MAKE) -C modules $@
@@ -86,10 +93,14 @@ installcheck: submake
 submake:
        $(MAKE) -C $(top_builddir)/src/test/regress pg_regress$(X)
 
-clean distclean maintainer-clean: clean-lib
+# pltclerrcodes.h is in the distribution tarball, so don't clean it here.
+clean distclean: clean-lib
        rm -f $(OBJS)
        rm -rf $(pg_regress_clean_files)
 ifeq ($(PORTNAME), win32)
        rm -f $(tclwithver).def
 endif
        $(MAKE) -C modules $@
+
+maintainer-clean: distclean
+       rm -f pltclerrcodes.h
index 807a6a3a94fd5d5fceca68e118fef3fcf0887837..e65e9e3ff71587156cf425c93404e557a01007be 100644 (file)
@@ -560,10 +560,10 @@ create function tcl_error_handling_test() returns text as $$
     global errorCode
     if {[catch { spi_exec "select no_such_column from foo;" }]} {
         array set errArray $errorCode
-        if {$errArray(SQLSTATE) == "42P01"} {
+        if {$errArray(condition) == "undefined_table"} {
             return "expected error: $errArray(message)"
         } else {
-            return "unexpected error: $errArray(SQLSTATE) $errArray(message)"
+            return "unexpected error: $errArray(condition) $errArray(message)"
         }
     } else {
         return "no error"
@@ -577,9 +577,9 @@ select tcl_error_handling_test();
 
 create temp table foo(f1 int);
 select tcl_error_handling_test();
-                    tcl_error_handling_test                     
-----------------------------------------------------------------
- unexpected error: 42703 column "no_such_column" does not exist
+                          tcl_error_handling_test                          
+---------------------------------------------------------------------------
+ unexpected error: undefined_column column "no_such_column" does not exist
 (1 row)
 
 drop table foo;
diff --git a/src/pl/tcl/generate-pltclerrcodes.pl b/src/pl/tcl/generate-pltclerrcodes.pl
new file mode 100644 (file)
index 0000000..144e159
--- /dev/null
@@ -0,0 +1,40 @@
+#!/usr/bin/perl
+#
+# Generate the pltclerrcodes.h header from errcodes.txt
+# Copyright (c) 2000-2016, PostgreSQL Global Development Group
+
+use warnings;
+use strict;
+
+print
+  "/* autogenerated from src/backend/utils/errcodes.txt, do not edit */\n";
+print "/* there is deliberately not an #ifndef PLTCLERRCODES_H here */\n";
+
+open my $errcodes, $ARGV[0] or die;
+
+while (<$errcodes>)
+{
+       chomp;
+
+       # Skip comments
+       next if /^#/;
+       next if /^\s*$/;
+
+       # Skip section headers
+       next if /^Section:/;
+
+       die unless /^([^\s]{5})\s+([EWS])\s+([^\s]+)(?:\s+)?([^\s]+)?/;
+
+       (my $sqlstate, my $type, my $errcode_macro, my $condition_name) =
+         ($1, $2, $3, $4);
+
+       # Skip non-errors
+       next unless $type eq 'E';
+
+       # Skip lines without PL/pgSQL condition names
+       next unless defined($condition_name);
+
+       print "{\n\t\"$condition_name\", $errcode_macro\n},\n\n";
+}
+
+close $errcodes;
index b1d66e31a6ed78daabb8d459503b4496f3ae0ac5..6ee4153ae62eafe0db3f9d5764ff78f6899d8e96 100644 (file)
@@ -188,6 +188,20 @@ static HTAB *pltcl_proc_htab = NULL;
 static FunctionCallInfo pltcl_current_fcinfo = NULL;
 static pltcl_proc_desc *pltcl_current_prodesc = NULL;
 
+/**********************************************************************
+ * Lookup table for SQLSTATE condition names
+ **********************************************************************/
+typedef struct
+{
+       const char *label;
+       int                     sqlerrstate;
+} TclExceptionNameMap;
+
+static const TclExceptionNameMap exception_name_map[] = {
+#include "pltclerrcodes.h"             /* pgrminclude ignore */
+       {NULL, 0}
+};
+
 /**********************************************************************
  * Forward declarations
  **********************************************************************/
@@ -213,6 +227,7 @@ static pltcl_proc_desc *compile_pltcl_function(Oid fn_oid, Oid tgreloid,
 static int pltcl_elog(ClientData cdata, Tcl_Interp *interp,
                   int objc, Tcl_Obj *const objv[]);
 static void pltcl_construct_errorCode(Tcl_Interp *interp, ErrorData *edata);
+static const char *pltcl_get_condition_name(int sqlstate);
 static int pltcl_quote(ClientData cdata, Tcl_Interp *interp,
                        int objc, Tcl_Obj *const objv[]);
 static int pltcl_argisnull(ClientData cdata, Tcl_Interp *interp,
@@ -1681,6 +1696,10 @@ pltcl_construct_errorCode(Tcl_Interp *interp, ErrorData *edata)
                                                         Tcl_NewStringObj("SQLSTATE", -1));
        Tcl_ListObjAppendElement(interp, obj,
                                  Tcl_NewStringObj(unpack_sql_state(edata->sqlerrcode), -1));
+       Tcl_ListObjAppendElement(interp, obj,
+                                                        Tcl_NewStringObj("condition", -1));
+       Tcl_ListObjAppendElement(interp, obj,
+                 Tcl_NewStringObj(pltcl_get_condition_name(edata->sqlerrcode), -1));
        Tcl_ListObjAppendElement(interp, obj,
                                                         Tcl_NewStringObj("message", -1));
        UTF_BEGIN;
@@ -1806,6 +1825,23 @@ pltcl_construct_errorCode(Tcl_Interp *interp, ErrorData *edata)
 }
 
 
+/**********************************************************************
+ * pltcl_get_condition_name()  - find name for SQLSTATE
+ **********************************************************************/
+static const char *
+pltcl_get_condition_name(int sqlstate)
+{
+       int                     i;
+
+       for (i = 0; exception_name_map[i].label != NULL; i++)
+       {
+               if (exception_name_map[i].sqlerrstate == sqlstate)
+                       return exception_name_map[i].label;
+       }
+       return "unrecognized_sqlstate";
+}
+
+
 /**********************************************************************
  * pltcl_quote()       - quote literal strings that are to
  *                       be used in SPI_execute query strings
index 36d9ef8539e676d5e33af0684a5ffc5a75560058..8df65a581655076a971d42367e09d59e21c14749 100644 (file)
@@ -602,10 +602,10 @@ create function tcl_error_handling_test() returns text as $$
     global errorCode
     if {[catch { spi_exec "select no_such_column from foo;" }]} {
         array set errArray $errorCode
-        if {$errArray(SQLSTATE) == "42P01"} {
+        if {$errArray(condition) == "undefined_table"} {
             return "expected error: $errArray(message)"
         } else {
-            return "unexpected error: $errArray(SQLSTATE) $errArray(message)"
+            return "unexpected error: $errArray(condition) $errArray(message)"
         }
     } else {
         return "no error"
index 60bcd7e7e631d1dfdf019396788e1d1faaaa5c15..ac1ba0a9f74bd58552261e7b6eff6b32867481b7 100644 (file)
@@ -350,6 +350,17 @@ s{PG_VERSION_STR "[^"]+"}{__STRINGIFY(x) #x\n#define __STRINGIFY2(z) __STRINGIFY
                );
        }
 
+       if ($self->{options}->{tcl}
+               && IsNewer(
+                       'src/pl/tcl/pltclerrcodes.h',
+                       'src/backend/utils/errcodes.txt'))
+       {
+               print "Generating pltclerrcodes.h...\n";
+               system(
+'perl src/pl/tcl/generate-pltclerrcodes.pl src/backend/utils/errcodes.txt > src/pl/tcl/pltclerrcodes.h'
+               );
+       }
+
        if (IsNewer(
                        'src/backend/utils/sort/qsort_tuple.c',
                        'src/backend/utils/sort/gen_qsort_tuple.pl'))