mirror of
https://github.com/postgres/postgres.git
synced 2025-04-22 23:02:54 +03:00
Improve PL/Tcl errorCode facility by providing decoded name for SQLSTATE.
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.
This commit is contained in:
parent
fb8d2a7f57
commit
cd37bb7859
@ -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
|
word is <literal>POSTGRES</literal>, the second word is the Postgres
|
||||||
version number, and additional words are field name/value pairs
|
version number, and additional words are field name/value pairs
|
||||||
providing detailed information about the error.
|
providing detailed information about the error.
|
||||||
Fields <varname>message</> and <varname>SQLSTATE</> (the error code
|
Fields <varname>SQLSTATE</>, <varname>condition</>,
|
||||||
shown in <xref linkend="errcodes-appendix">) are always supplied.
|
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
|
Fields that may be present include
|
||||||
<varname>detail</>, <varname>hint</>, <varname>context</>,
|
<varname>detail</>, <varname>hint</>, <varname>context</>,
|
||||||
<varname>schema</>, <varname>table</>, <varname>column</>,
|
<varname>schema</>, <varname>table</>, <varname>column</>,
|
||||||
<varname>datatype</>, <varname>constraint</>,
|
<varname>datatype</>, <varname>constraint</>,
|
||||||
<varname>statement</>, <varname>cursor_position</>,
|
<varname>statement</>, <varname>cursor_position</>,
|
||||||
<varname>filename</>, <varname>lineno</> and
|
<varname>filename</>, <varname>lineno</>, and
|
||||||
<varname>funcname</>.
|
<varname>funcname</>.
|
||||||
</para>
|
</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 {[catch { spi_exec $sql_command }]} {
|
||||||
if {[lindex $::errorCode 0] == "POSTGRES"} {
|
if {[lindex $::errorCode 0] == "POSTGRES"} {
|
||||||
array set errorArray $::errorCode
|
array set errorArray $::errorCode
|
||||||
if {$errorArray(SQLSTATE) == "42P01"} { # UNDEFINED_TABLE
|
if {$errorArray(condition) == "undefined_table"} {
|
||||||
# deal with missing table
|
# deal with missing table
|
||||||
} else {
|
} else {
|
||||||
# deal with some other type of SQL error
|
# deal with some other type of SQL error
|
||||||
|
@ -15,6 +15,9 @@
|
|||||||
# src/pl/plpgsql/src/plerrcodes.h
|
# src/pl/plpgsql/src/plerrcodes.h
|
||||||
# a list of PL/pgSQL condition names and their SQLSTATE codes
|
# 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
|
# doc/src/sgml/errcodes-list.sgml
|
||||||
# a SGML table of error codes for inclusion in the documentation
|
# a SGML table of error codes for inclusion in the documentation
|
||||||
#
|
#
|
||||||
|
2
src/pl/tcl/.gitignore
vendored
2
src/pl/tcl/.gitignore
vendored
@ -1,3 +1,5 @@
|
|||||||
|
/pltclerrcodes.h
|
||||||
|
|
||||||
# Generated subdirectories
|
# Generated subdirectories
|
||||||
/log/
|
/log/
|
||||||
/results/
|
/results/
|
||||||
|
@ -13,7 +13,6 @@ include $(top_builddir)/src/Makefile.global
|
|||||||
|
|
||||||
override CPPFLAGS := $(TCL_INCLUDE_SPEC) $(CPPFLAGS)
|
override CPPFLAGS := $(TCL_INCLUDE_SPEC) $(CPPFLAGS)
|
||||||
|
|
||||||
|
|
||||||
# On Windows, we don't link directly with the Tcl library; see below
|
# On Windows, we don't link directly with the Tcl library; see below
|
||||||
ifneq ($(PORTNAME), win32)
|
ifneq ($(PORTNAME), win32)
|
||||||
SHLIB_LINK = $(TCL_LIB_SPEC) $(TCL_LIBS) -lc
|
SHLIB_LINK = $(TCL_LIB_SPEC) $(TCL_LIBS) -lc
|
||||||
@ -56,6 +55,14 @@ include $(top_srcdir)/src/Makefile.shlib
|
|||||||
all: all-lib
|
all: all-lib
|
||||||
$(MAKE) -C modules $@
|
$(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
|
install: all install-lib install-data
|
||||||
$(MAKE) -C modules $@
|
$(MAKE) -C modules $@
|
||||||
@ -86,10 +93,14 @@ installcheck: submake
|
|||||||
submake:
|
submake:
|
||||||
$(MAKE) -C $(top_builddir)/src/test/regress pg_regress$(X)
|
$(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 -f $(OBJS)
|
||||||
rm -rf $(pg_regress_clean_files)
|
rm -rf $(pg_regress_clean_files)
|
||||||
ifeq ($(PORTNAME), win32)
|
ifeq ($(PORTNAME), win32)
|
||||||
rm -f $(tclwithver).def
|
rm -f $(tclwithver).def
|
||||||
endif
|
endif
|
||||||
$(MAKE) -C modules $@
|
$(MAKE) -C modules $@
|
||||||
|
|
||||||
|
maintainer-clean: distclean
|
||||||
|
rm -f pltclerrcodes.h
|
||||||
|
@ -560,10 +560,10 @@ create function tcl_error_handling_test() returns text as $$
|
|||||||
global errorCode
|
global errorCode
|
||||||
if {[catch { spi_exec "select no_such_column from foo;" }]} {
|
if {[catch { spi_exec "select no_such_column from foo;" }]} {
|
||||||
array set errArray $errorCode
|
array set errArray $errorCode
|
||||||
if {$errArray(SQLSTATE) == "42P01"} {
|
if {$errArray(condition) == "undefined_table"} {
|
||||||
return "expected error: $errArray(message)"
|
return "expected error: $errArray(message)"
|
||||||
} else {
|
} else {
|
||||||
return "unexpected error: $errArray(SQLSTATE) $errArray(message)"
|
return "unexpected error: $errArray(condition) $errArray(message)"
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return "no error"
|
return "no error"
|
||||||
@ -578,8 +578,8 @@ select tcl_error_handling_test();
|
|||||||
create temp table foo(f1 int);
|
create temp table foo(f1 int);
|
||||||
select tcl_error_handling_test();
|
select tcl_error_handling_test();
|
||||||
tcl_error_handling_test
|
tcl_error_handling_test
|
||||||
----------------------------------------------------------------
|
---------------------------------------------------------------------------
|
||||||
unexpected error: 42703 column "no_such_column" does not exist
|
unexpected error: undefined_column column "no_such_column" does not exist
|
||||||
(1 row)
|
(1 row)
|
||||||
|
|
||||||
drop table foo;
|
drop table foo;
|
||||||
|
40
src/pl/tcl/generate-pltclerrcodes.pl
Normal file
40
src/pl/tcl/generate-pltclerrcodes.pl
Normal file
@ -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;
|
@ -188,6 +188,20 @@ static HTAB *pltcl_proc_htab = NULL;
|
|||||||
static FunctionCallInfo pltcl_current_fcinfo = NULL;
|
static FunctionCallInfo pltcl_current_fcinfo = NULL;
|
||||||
static pltcl_proc_desc *pltcl_current_prodesc = 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
|
* 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,
|
static int pltcl_elog(ClientData cdata, Tcl_Interp *interp,
|
||||||
int objc, Tcl_Obj *const objv[]);
|
int objc, Tcl_Obj *const objv[]);
|
||||||
static void pltcl_construct_errorCode(Tcl_Interp *interp, ErrorData *edata);
|
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,
|
static int pltcl_quote(ClientData cdata, Tcl_Interp *interp,
|
||||||
int objc, Tcl_Obj *const objv[]);
|
int objc, Tcl_Obj *const objv[]);
|
||||||
static int pltcl_argisnull(ClientData cdata, Tcl_Interp *interp,
|
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_NewStringObj("SQLSTATE", -1));
|
||||||
Tcl_ListObjAppendElement(interp, obj,
|
Tcl_ListObjAppendElement(interp, obj,
|
||||||
Tcl_NewStringObj(unpack_sql_state(edata->sqlerrcode), -1));
|
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_ListObjAppendElement(interp, obj,
|
||||||
Tcl_NewStringObj("message", -1));
|
Tcl_NewStringObj("message", -1));
|
||||||
UTF_BEGIN;
|
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
|
* pltcl_quote() - quote literal strings that are to
|
||||||
* be used in SPI_execute query strings
|
* be used in SPI_execute query strings
|
||||||
|
@ -602,10 +602,10 @@ create function tcl_error_handling_test() returns text as $$
|
|||||||
global errorCode
|
global errorCode
|
||||||
if {[catch { spi_exec "select no_such_column from foo;" }]} {
|
if {[catch { spi_exec "select no_such_column from foo;" }]} {
|
||||||
array set errArray $errorCode
|
array set errArray $errorCode
|
||||||
if {$errArray(SQLSTATE) == "42P01"} {
|
if {$errArray(condition) == "undefined_table"} {
|
||||||
return "expected error: $errArray(message)"
|
return "expected error: $errArray(message)"
|
||||||
} else {
|
} else {
|
||||||
return "unexpected error: $errArray(SQLSTATE) $errArray(message)"
|
return "unexpected error: $errArray(condition) $errArray(message)"
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return "no error"
|
return "no error"
|
||||||
|
@ -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(
|
if (IsNewer(
|
||||||
'src/backend/utils/sort/qsort_tuple.c',
|
'src/backend/utils/sort/qsort_tuple.c',
|
||||||
'src/backend/utils/sort/gen_qsort_tuple.pl'))
|
'src/backend/utils/sort/gen_qsort_tuple.pl'))
|
||||||
|
Loading…
x
Reference in New Issue
Block a user