From f690920a752fa8e59dc9536dd14194b2141163d2 Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Thu, 24 Apr 2003 21:16:45 +0000 Subject: [PATCH] Infrastructure for upgraded error reporting mechanism. elog.c is rewritten and the protocol is changed, but most elog calls are still elog calls. Also, we need to contemplate mechanisms for controlling all this functionality --- eg, how much stuff should appear in the postmaster log? And what API should libpq expose for it? --- config/c-compiler.m4 | 28 +- configure | 105 ++ configure.in | 3 +- doc/src/sgml/protocol.sgml | 10 +- src/backend/catalog/namespace.c | 6 +- src/backend/commands/copy.c | 37 +- src/backend/commands/explain.c | 12 +- src/backend/executor/spi.c | 4 +- src/backend/lib/stringinfo.c | 123 +- src/backend/nodes/outfuncs.c | 4 +- src/backend/parser/parse_func.c | 6 +- src/backend/parser/parse_type.c | 10 +- src/backend/parser/scan.l | 19 +- src/backend/tcop/postgres.c | 6 +- src/backend/utils/adt/ruleutils.c | 6 +- src/backend/utils/adt/varlena.c | 8 +- src/backend/utils/error/elog.c | 1276 +++++++++++++------- src/include/commands/copy.h | 6 +- src/include/lib/stringinfo.h | 24 +- src/include/libpq/pqcomm.h | 4 +- src/include/pg_config.h.in | 6 + src/include/utils/elog.h | 230 +++- src/interfaces/libpq/fe-connect.c | 11 +- src/interfaces/libpq/fe-exec.c | 162 ++- src/interfaces/libpq/libpq-int.h | 27 +- src/pl/plpgsql/src/pl_comp.c | 51 +- src/pl/plpgsql/src/pl_exec.c | 219 ++-- src/pl/plpgsql/src/plpgsql.h | 7 +- src/test/regress/expected/alter_table.out | 3 +- src/test/regress/expected/copy2.out | 12 +- src/test/regress/expected/domain.out | 9 +- src/test/regress/expected/plpgsql.out | 4 + src/test/regress/output/constraints.source | 3 +- 33 files changed, 1713 insertions(+), 728 deletions(-) diff --git a/config/c-compiler.m4 b/config/c-compiler.m4 index 52cb7c0915..5d72e1377b 100644 --- a/config/c-compiler.m4 +++ b/config/c-compiler.m4 @@ -1,5 +1,5 @@ # Macros to detect C compiler features -# $Header: /cvsroot/pgsql/config/c-compiler.m4,v 1.7 2003/04/06 22:45:22 petere Exp $ +# $Header: /cvsroot/pgsql/config/c-compiler.m4,v 1.8 2003/04/24 21:16:42 tgl Exp $ # PGAC_C_SIGNED @@ -94,3 +94,29 @@ AC_DEFINE_UNQUOTED(AS_TR_CPP(alignof_$1), [$AS_TR_SH([pgac_cv_alignof_$1])], [The alignment requirement of a `$1'.]) ])# PGAC_CHECK_ALIGNOF + + +# PGAC_C_FUNCNAME_SUPPORT +# ------------- +# Check if the C compiler understands __func__ (C99) or __FUNCTION__ (gcc). +# Define HAVE_FUNCNAME__FUNC or HAVE_FUNCNAME__FUNCTION accordingly. +AC_DEFUN([PGAC_C_FUNCNAME_SUPPORT], +[AC_CACHE_CHECK(for __func__, pgac_cv_funcname_func_support, +[AC_TRY_COMPILE([#include ], +[printf("%s\n", __func__);], +[pgac_cv_funcname_func_support=yes], +[pgac_cv_funcname_func_support=no])]) +if test x"$pgac_cv_funcname_func_support" = xyes ; then +AC_DEFINE(HAVE_FUNCNAME__FUNC, 1, + [Define to 1 if your compiler understands __func__.]) +else +AC_CACHE_CHECK(for __FUNCTION__, pgac_cv_funcname_function_support, +[AC_TRY_COMPILE([#include ], +[printf("%s\n", __FUNCTION__);], +[pgac_cv_funcname_function_support=yes], +[pgac_cv_funcname_function_support=no])]) +if test x"$pgac_cv_funcname_function_support" = xyes ; then +AC_DEFINE(HAVE_FUNCNAME__FUNCTION, 1, + [Define to 1 if your compiler understands __FUNCTION__.]) +fi +fi])# PGAC_C_FUNCNAME_SUPPORT diff --git a/configure b/configure index 150c958ca0..85873ff60f 100755 --- a/configure +++ b/configure @@ -9051,6 +9051,111 @@ _ACEOF fi +echo "$as_me:$LINENO: checking for __func__" >&5 +echo $ECHO_N "checking for __func__... $ECHO_C" >&6 +if test "${pgac_cv_funcname_func_support+set}" = set; then + echo $ECHO_N "(cached) $ECHO_C" >&6 +else + cat >conftest.$ac_ext <<_ACEOF +#line $LINENO "configure" +#include "confdefs.h" +#include +#ifdef F77_DUMMY_MAIN +# ifdef __cplusplus + extern "C" +# endif + int F77_DUMMY_MAIN() { return 1; } +#endif +int +main () +{ +printf("%s\n", __func__); + ; + return 0; +} +_ACEOF +rm -f conftest.$ac_objext +if { (eval echo "$as_me:$LINENO: \"$ac_compile\"") >&5 + (eval $ac_compile) 2>&5 + ac_status=$? + echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } && + { ac_try='test -s conftest.$ac_objext' + { (eval echo "$as_me:$LINENO: \"$ac_try\"") >&5 + (eval $ac_try) 2>&5 + ac_status=$? + echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); }; }; then + pgac_cv_funcname_func_support=yes +else + echo "$as_me: failed program was:" >&5 +cat conftest.$ac_ext >&5 +pgac_cv_funcname_func_support=no +fi +rm -f conftest.$ac_objext conftest.$ac_ext +fi +echo "$as_me:$LINENO: result: $pgac_cv_funcname_func_support" >&5 +echo "${ECHO_T}$pgac_cv_funcname_func_support" >&6 +if test x"$pgac_cv_funcname_func_support" = xyes ; then + +cat >>confdefs.h <<\_ACEOF +#define HAVE_FUNCNAME__FUNC 1 +_ACEOF + +else +echo "$as_me:$LINENO: checking for __FUNCTION__" >&5 +echo $ECHO_N "checking for __FUNCTION__... $ECHO_C" >&6 +if test "${pgac_cv_funcname_function_support+set}" = set; then + echo $ECHO_N "(cached) $ECHO_C" >&6 +else + cat >conftest.$ac_ext <<_ACEOF +#line $LINENO "configure" +#include "confdefs.h" +#include +#ifdef F77_DUMMY_MAIN +# ifdef __cplusplus + extern "C" +# endif + int F77_DUMMY_MAIN() { return 1; } +#endif +int +main () +{ +printf("%s\n", __FUNCTION__); + ; + return 0; +} +_ACEOF +rm -f conftest.$ac_objext +if { (eval echo "$as_me:$LINENO: \"$ac_compile\"") >&5 + (eval $ac_compile) 2>&5 + ac_status=$? + echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } && + { ac_try='test -s conftest.$ac_objext' + { (eval echo "$as_me:$LINENO: \"$ac_try\"") >&5 + (eval $ac_try) 2>&5 + ac_status=$? + echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); }; }; then + pgac_cv_funcname_function_support=yes +else + echo "$as_me: failed program was:" >&5 +cat conftest.$ac_ext >&5 +pgac_cv_funcname_function_support=no +fi +rm -f conftest.$ac_objext conftest.$ac_ext +fi +echo "$as_me:$LINENO: result: $pgac_cv_funcname_function_support" >&5 +echo "${ECHO_T}$pgac_cv_funcname_function_support" >&6 +if test x"$pgac_cv_funcname_function_support" = xyes ; then + +cat >>confdefs.h <<\_ACEOF +#define HAVE_FUNCNAME__FUNCTION 1 +_ACEOF + +fi +fi echo "$as_me:$LINENO: checking whether struct tm is in sys/time.h or time.h" >&5 echo $ECHO_N "checking whether struct tm is in sys/time.h or time.h... $ECHO_C" >&6 if test "${ac_cv_struct_tm+set}" = set; then diff --git a/configure.in b/configure.in index 9e50e487c9..10843597a5 100644 --- a/configure.in +++ b/configure.in @@ -1,5 +1,5 @@ dnl Process this file with autoconf to produce a configure script. -dnl $Header: /cvsroot/pgsql/configure.in,v 1.243 2003/04/22 02:18:09 momjian Exp $ +dnl $Header: /cvsroot/pgsql/configure.in,v 1.244 2003/04/24 21:16:42 tgl Exp $ dnl dnl Developers, please strive to achieve this order: dnl @@ -733,6 +733,7 @@ AC_C_INLINE AC_C_STRINGIZE PGAC_C_SIGNED AC_C_VOLATILE +PGAC_C_FUNCNAME_SUPPORT AC_STRUCT_TIMEZONE PGAC_UNION_SEMUN PGAC_STRUCT_SOCKADDR_UN diff --git a/doc/src/sgml/protocol.sgml b/doc/src/sgml/protocol.sgml index 52d2a60c3b..7b5f9593a9 100644 --- a/doc/src/sgml/protocol.sgml +++ b/doc/src/sgml/protocol.sgml @@ -1,4 +1,4 @@ - + Frontend/Backend Protocol @@ -3862,6 +3862,14 @@ byte (except for startup packets, which have no type byte). Also note that PasswordMessage now has a type byte. + +ErrorResponse and NoticeResponse ('E' and 'N') +messages now contain multiple fields, from which the client code may +assemble an error message of the desired level of verbosity. Note that +individual fields will typically not end with a newline, whereas the single +string sent in the older protocol always did. + + COPY data is now encapsulated into CopyData and CopyDone messages. There is a well-defined way to recover from errors during COPY. The special diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c index 90c26288f5..842a36c57b 100644 --- a/src/backend/catalog/namespace.c +++ b/src/backend/catalog/namespace.c @@ -13,7 +13,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/catalog/namespace.c,v 1.49 2003/03/20 03:34:55 momjian Exp $ + * $Header: /cvsroot/pgsql/src/backend/catalog/namespace.c,v 1.50 2003/04/24 21:16:42 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -1280,7 +1280,7 @@ NameListToString(List *names) { if (l != names) appendStringInfoChar(&string, '.'); - appendStringInfo(&string, "%s", strVal(lfirst(l))); + appendStringInfoString(&string, strVal(lfirst(l))); } return string.data; @@ -1305,7 +1305,7 @@ NameListToQuotedString(List *names) { if (l != names) appendStringInfoChar(&string, '.'); - appendStringInfo(&string, "%s", quote_identifier(strVal(lfirst(l)))); + appendStringInfoString(&string, quote_identifier(strVal(lfirst(l)))); } return string.data; diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c index 40948e3a3b..7cb530a3cd 100644 --- a/src/backend/commands/copy.c +++ b/src/backend/commands/copy.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/commands/copy.c,v 1.195 2003/04/22 00:08:06 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/commands/copy.c,v 1.196 2003/04/24 21:16:42 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -100,13 +100,13 @@ static const char BinarySignature[12] = "PGBCOPY\n\377\r\n\0"; * Static communication variables ... pretty grotty, but COPY has * never been reentrant... */ -int copy_lineno = 0; /* exported for use by elog() -- dz */ - static CopyDest copy_dest; static FILE *copy_file; /* if copy_dest == COPY_FILE */ static StringInfo copy_msgbuf; /* if copy_dest == COPY_NEW_FE */ static bool fe_eof; /* true if detected end of copy data */ -static EolType eol_type; +static EolType eol_type; /* EOL type of input */ +static int copy_lineno; /* line number for error messages */ + /* * These static variables are used to avoid incurring overhead for each @@ -1000,6 +1000,16 @@ CopyTo(Relation rel, List *attnumlist, bool binary, bool oids, } +/* + * error context callback for COPY FROM + */ +static void +copy_in_error_callback(void *arg) +{ + errcontext("COPY FROM, line %d", copy_lineno); +} + + /* * Copy FROM file to relation. */ @@ -1032,6 +1042,7 @@ CopyFrom(Relation rel, List *attnumlist, bool binary, bool oids, ExprState **defexprs; /* array of default att expressions */ ExprContext *econtext; /* used for ExecEvalExpr for default atts */ MemoryContext oldcontext = CurrentMemoryContext; + ErrorContextCallback errcontext; tupDesc = RelationGetDescr(rel); attr = tupDesc->attrs; @@ -1188,16 +1199,22 @@ CopyFrom(Relation rel, List *attnumlist, bool binary, bool oids, values = (Datum *) palloc(num_phys_attrs * sizeof(Datum)); nulls = (char *) palloc(num_phys_attrs * sizeof(char)); - /* Initialize static variables */ - copy_lineno = 0; - eol_type = EOL_UNKNOWN; - fe_eof = false; - /* Make room for a PARAM_EXEC value for domain constraint checks */ if (hasConstraints) econtext->ecxt_param_exec_vals = (ParamExecData *) palloc0(sizeof(ParamExecData)); + /* Initialize static variables */ + fe_eof = false; + eol_type = EOL_UNKNOWN; + copy_lineno = 0; + + /* Set up callback to identify error line number */ + errcontext.callback = copy_in_error_callback; + errcontext.arg = NULL; + errcontext.previous = error_context_stack; + error_context_stack = &errcontext; + while (!done) { bool skip_tuple; @@ -1502,7 +1519,7 @@ CopyFrom(Relation rel, List *attnumlist, bool binary, bool oids, /* * Done, clean up */ - copy_lineno = 0; + error_context_stack = errcontext.previous; MemoryContextSwitchTo(oldcontext); diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c index 09d422f85c..d117d2e9a2 100644 --- a/src/backend/commands/explain.c +++ b/src/backend/commands/explain.c @@ -7,7 +7,7 @@ * Portions Copyright (c) 1994-5, Regents of the University of California * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/commands/explain.c,v 1.105 2003/04/03 22:35:48 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/commands/explain.c,v 1.106 2003/04/24 21:16:42 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -320,7 +320,7 @@ explain_outNode(StringInfo str, if (plan == NULL) { - appendStringInfo(str, "\n"); + appendStringInfoChar(str, '\n'); return; } @@ -476,13 +476,13 @@ explain_outNode(StringInfo str, break; } - appendStringInfo(str, pname); + appendStringInfoString(str, pname); switch (nodeTag(plan)) { case T_IndexScan: if (ScanDirectionIsBackward(((IndexScan *) plan)->indxorderdir)) - appendStringInfo(str, " Backward"); - appendStringInfo(str, " using "); + appendStringInfoString(str, " Backward"); + appendStringInfoString(str, " using "); i = 0; foreach(l, ((IndexScan *) plan)->indxid) { @@ -590,7 +590,7 @@ explain_outNode(StringInfo str, appendStringInfo(str, " (never executed)"); } } - appendStringInfo(str, "\n"); + appendStringInfoChar(str, '\n'); /* quals, sort keys, etc */ switch (nodeTag(plan)) diff --git a/src/backend/executor/spi.c b/src/backend/executor/spi.c index 7184fbf9ba..faceb1ec73 100644 --- a/src/backend/executor/spi.c +++ b/src/backend/executor/spi.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/executor/spi.c,v 1.89 2003/03/27 16:51:28 momjian Exp $ + * $Header: /cvsroot/pgsql/src/backend/executor/spi.c,v 1.90 2003/04/24 21:16:43 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -995,7 +995,7 @@ _SPI_execute(const char *src, int tcount, _SPI_plan *plan) * Parse the request string into a list of raw parse trees. */ initStringInfo(&stri); - appendStringInfo(&stri, "%s", src); + appendStringInfoString(&stri, src); raw_parsetree_list = pg_parse_query(&stri, argtypes, nargs); diff --git a/src/backend/lib/stringinfo.c b/src/backend/lib/stringinfo.c index 0f758b1bd2..03251beed9 100644 --- a/src/backend/lib/stringinfo.c +++ b/src/backend/lib/stringinfo.c @@ -9,7 +9,7 @@ * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $Id: stringinfo.c,v 1.33 2003/04/19 00:02:29 tgl Exp $ + * $Id: stringinfo.c,v 1.34 2003/04/24 21:16:43 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -56,61 +56,102 @@ initStringInfo(StringInfo str) /* * appendStringInfo * - * Format text data under the control of fmt (an sprintf-like format string) + * Format text data under the control of fmt (an sprintf-style format string) * and append it to whatever is already in str. More space is allocated * to str if necessary. This is sort of like a combination of sprintf and * strcat. */ void -appendStringInfo(StringInfo str, const char *fmt,...) +appendStringInfo(StringInfo str, const char *fmt, ...) +{ + for (;;) + { + va_list args; + bool success; + + /* Try to format the data. */ + va_start(args, fmt); + success = appendStringInfoVA(str, fmt, args); + va_end(args); + + if (success) + break; + + /* Double the buffer size and try again. */ + enlargeStringInfo(str, str->maxlen); + } +} + +/* + * appendStringInfoVA + * + * Attempt to format text data under the control of fmt (an sprintf-style + * format string) and append it to whatever is already in str. If successful + * return true; if not (because there's not enough space), return false + * without modifying str. Typically the caller would enlarge str and retry + * on false return --- see appendStringInfo for standard usage pattern. + * + * XXX This API is ugly, but there seems no alternative given the C spec's + * restrictions on what can portably be done with va_list arguments: you have + * to redo va_start before you can rescan the argument list, and we can't do + * that from here. + */ +bool +appendStringInfoVA(StringInfo str, const char *fmt, va_list args) { - va_list args; int avail, nprinted; Assert(str != NULL); - for (;;) - { - /* - * Try to format the given string into the available space; but if - * there's hardly any space, don't bother trying, just fall - * through to enlarge the buffer first. - */ - avail = str->maxlen - str->len - 1; - if (avail > 16) - { - /* - * Assert check here is to catch buggy vsnprintf that overruns - * the specified buffer length. Solaris 7 in 64-bit mode is - * an example of a platform with such a bug. - */ + /* + * If there's hardly any space, don't bother trying, just fail to make + * the caller enlarge the buffer first. + */ + avail = str->maxlen - str->len - 1; + if (avail < 16) + return false; + + /* + * Assert check here is to catch buggy vsnprintf that overruns + * the specified buffer length. Solaris 7 in 64-bit mode is + * an example of a platform with such a bug. + */ #ifdef USE_ASSERT_CHECKING - str->data[str->maxlen - 1] = '\0'; + str->data[str->maxlen - 1] = '\0'; #endif - va_start(args, fmt); - nprinted = vsnprintf(str->data + str->len, avail, - fmt, args); - va_end(args); + nprinted = vsnprintf(str->data + str->len, avail, fmt, args); - Assert(str->data[str->maxlen - 1] == '\0'); + Assert(str->data[str->maxlen - 1] == '\0'); - /* - * Note: some versions of vsnprintf return the number of chars - * actually stored, but at least one returns -1 on failure. Be - * conservative about believing whether the print worked. - */ - if (nprinted >= 0 && nprinted < avail - 1) - { - /* Success. Note nprinted does not include trailing null. */ - str->len += nprinted; - break; - } - } - /* Double the buffer size and try again. */ - enlargeStringInfo(str, str->maxlen); + /* + * Note: some versions of vsnprintf return the number of chars + * actually stored, but at least one returns -1 on failure. Be + * conservative about believing whether the print worked. + */ + if (nprinted >= 0 && nprinted < avail - 1) + { + /* Success. Note nprinted does not include trailing null. */ + str->len += nprinted; + return true; } + + /* Restore the trailing null so that str is unmodified. */ + str->data[str->len] = '\0'; + return false; +} + +/* + * appendStringInfoString + * + * Append a null-terminated string to str. + * Like appendStringInfo(str, "%s", s) but faster. + */ +void +appendStringInfoString(StringInfo str, const char *s) +{ + appendBinaryStringInfo(str, s, strlen(s)); } /* @@ -163,8 +204,8 @@ appendBinaryStringInfo(StringInfo str, const char *data, int datalen) * Make sure there is enough space for 'needed' more bytes * ('needed' does not include the terminating null). * - * External callers need not concern themselves with this, since all - * stringinfo.c routines do it automatically. However, if a caller + * External callers usually need not concern themselves with this, since + * all stringinfo.c routines do it automatically. However, if a caller * knows that a StringInfo will eventually become X bytes large, it * can save some palloc overhead by enlarging the buffer before starting * to store data in it. diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index f5d285ee09..45c7a2a301 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/nodes/outfuncs.c,v 1.202 2003/04/08 23:20:01 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/nodes/outfuncs.c,v 1.203 2003/04/24 21:16:43 tgl Exp $ * * NOTES * Every node type that can appear in stored rules' parsetrees *must* @@ -39,7 +39,7 @@ /* Write the label for the node type */ #define WRITE_NODE_TYPE(nodelabel) \ - appendStringInfo(str, nodelabel) + appendStringInfoString(str, nodelabel) /* Write an integer field (anything written as ":fldname %d") */ #define WRITE_INT_FIELD(fldname) \ diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c index 59690f4aef..7cbef96536 100644 --- a/src/backend/parser/parse_func.c +++ b/src/backend/parser/parse_func.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/parser/parse_func.c,v 1.145 2003/04/08 23:20:02 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/parser/parse_func.c,v 1.146 2003/04/24 21:16:43 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -1288,8 +1288,8 @@ func_error(const char *caller, List *funcname, for (i = 0; i < nargs; i++) { if (i) - appendStringInfo(&argbuf, ", "); - appendStringInfo(&argbuf, format_type_be(argtypes[i])); + appendStringInfoString(&argbuf, ", "); + appendStringInfoString(&argbuf, format_type_be(argtypes[i])); } if (caller == NULL) diff --git a/src/backend/parser/parse_type.c b/src/backend/parser/parse_type.c index 1f0b7639d5..6d038080ea 100644 --- a/src/backend/parser/parse_type.c +++ b/src/backend/parser/parse_type.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/parser/parse_type.c,v 1.54 2003/03/10 03:53:51 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/parser/parse_type.c,v 1.55 2003/04/24 21:16:43 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -153,13 +153,13 @@ TypeNameToString(const TypeName *typename) { if (l != typename->names) appendStringInfoChar(&string, '.'); - appendStringInfo(&string, "%s", strVal(lfirst(l))); + appendStringInfoString(&string, strVal(lfirst(l))); } } else { /* Look up internally-specified type */ - appendStringInfo(&string, "%s", format_type_be(typename->typeid)); + appendStringInfoString(&string, format_type_be(typename->typeid)); } /* @@ -167,10 +167,10 @@ TypeNameToString(const TypeName *typename) * LookupTypeName */ if (typename->pct_type) - appendStringInfo(&string, "%%TYPE"); + appendStringInfoString(&string, "%TYPE"); if (typename->arrayBounds != NIL) - appendStringInfo(&string, "[]"); + appendStringInfoString(&string, "[]"); return string.data; } diff --git a/src/backend/parser/scan.l b/src/backend/parser/scan.l index 9fc28a859e..05b488643e 100644 --- a/src/backend/parser/scan.l +++ b/src/backend/parser/scan.l @@ -9,7 +9,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/parser/scan.l,v 1.103 2002/11/11 03:33:38 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/parser/scan.l,v 1.104 2003/04/24 21:16:43 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -33,7 +33,7 @@ #define YY_READ_BUF_SIZE 16777216 /* Avoid exit() on fatal scanner errors (a bit ugly -- see yy_fatal_error) */ -#define fprintf(file, fmt, msg) elog(FATAL, "%s", (msg)) +#define fprintf(file, fmt, msg) ereport(FATAL, (errmsg_internal("%s", msg))) extern YYSTYPE yylval; @@ -575,12 +575,19 @@ void yyerror(const char *message) { const char *loc = token_start ? token_start : yytext; + int cursorpos; + + /* in multibyte encodings, return index in characters not bytes */ + cursorpos = pg_mbstrlen_with_len(scanbuf, loc - scanbuf) + 1; if (*loc == YY_END_OF_BUFFER_CHAR) - elog(ERROR, "parser: %s at end of input", message); + ereport(ERROR, + (errmsg("parser: %s at end of input", message), + errposition(cursorpos))); else - elog(ERROR, "parser: %s at or near \"%s\" at character %d", - message, loc, (int) (loc - scanbuf + 1)); + ereport(ERROR, + (errmsg("parser: %s at or near \"%s\"", message, loc), + errposition(cursorpos))); } @@ -591,7 +598,7 @@ void scanner_init(StringInfo str) { /* - * Might be left over after elog() + * Might be left over after ereport() */ if (YY_CURRENT_BUFFER) yy_delete_buffer(YY_CURRENT_BUFFER); diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c index 5c51a1056a..bc884906ca 100644 --- a/src/backend/tcop/postgres.c +++ b/src/backend/tcop/postgres.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/tcop/postgres.c,v 1.323 2003/04/22 00:08:07 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/tcop/postgres.c,v 1.324 2003/04/24 21:16:43 tgl Exp $ * * NOTES * this is the "main" module of the postgres backend and @@ -349,7 +349,7 @@ pg_parse_and_rewrite(char *query_string, /* string to execute */ StringInfoData stri; initStringInfo(&stri); - appendStringInfo(&stri, "%s", query_string); + appendStringInfoString(&stri, query_string); /* * (1) parse the request string into a list of raw parse trees. @@ -1831,7 +1831,7 @@ PostgresMain(int argc, char *argv[], const char *username) if (!IsUnderPostmaster) { puts("\nPOSTGRES backend interactive interface "); - puts("$Revision: 1.323 $ $Date: 2003/04/22 00:08:07 $\n"); + puts("$Revision: 1.324 $ $Date: 2003/04/24 21:16:43 $\n"); } /* diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c index 31135ff97f..581c5b20ec 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -3,7 +3,7 @@ * back to source text * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/utils/adt/ruleutils.c,v 1.138 2003/04/08 23:20:02 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/utils/adt/ruleutils.c,v 1.139 2003/04/24 21:16:43 tgl Exp $ * * This software is copyrighted by Jan Wieck - Hamburg. * @@ -921,7 +921,7 @@ pg_get_constraintdef(PG_FUNCTION_ARGS) constraintId); /* Append the constraint source */ - appendStringInfo(&buf, DatumGetCString(DirectFunctionCall1(textout, val))); + appendStringInfoString(&buf, DatumGetCString(DirectFunctionCall1(textout, val))); break; } @@ -2846,7 +2846,7 @@ get_const_expr(Const *constval, deparse_context *context) */ if (strspn(extval, "0123456789+-eE.") == strlen(extval)) { - appendStringInfo(buf, extval); + appendStringInfoString(buf, extval); if (strcspn(extval, "eE.") != strlen(extval)) isfloat = true; /* it looks like a float */ } diff --git a/src/backend/utils/adt/varlena.c b/src/backend/utils/adt/varlena.c index 974e7f8fc6..7f928ec8ac 100644 --- a/src/backend/utils/adt/varlena.c +++ b/src/backend/utils/adt/varlena.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/utils/adt/varlena.c,v 1.95 2003/03/10 22:28:18 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/utils/adt/varlena.c,v 1.96 2003/04/24 21:16:43 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -1696,8 +1696,8 @@ replace_text(PG_FUNCTION_ARGS) left_text = LEFT(buf_text, from_sub_text); right_text = RIGHT(buf_text, from_sub_text, from_sub_text_len); - appendStringInfo(str, PG_TEXT_GET_STR(left_text)); - appendStringInfo(str, to_sub_str); + appendStringInfoString(str, PG_TEXT_GET_STR(left_text)); + appendStringInfoString(str, to_sub_str); pfree(buf_text); pfree(left_text); @@ -1705,7 +1705,7 @@ replace_text(PG_FUNCTION_ARGS) curr_posn = TEXTPOS(buf_text, from_sub_text); } - appendStringInfo(str, PG_TEXT_GET_STR(buf_text)); + appendStringInfoString(str, PG_TEXT_GET_STR(buf_text)); pfree(buf_text); ret_text = PG_STR_GET_TEXT(str->data); diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c index 01250f9a2f..6ca9f38401 100644 --- a/src/backend/utils/error/elog.c +++ b/src/backend/utils/error/elog.c @@ -1,14 +1,43 @@ /*------------------------------------------------------------------------- * * elog.c - * error logger + * error logging and reporting + * + * Some notes about recursion and errors during error processing: + * + * We need to be robust about recursive-error scenarios --- for example, + * if we run out of memory, it's important to be able to report that fact. + * There are a number of considerations that go into this. + * + * First, distinguish between re-entrant use and actual recursion. It + * is possible for an error or warning message to be emitted while the + * parameters for an error message are being computed. In this case + * errstart has been called for the outer message, and some field values + * may have already been saved, but we are not actually recursing. We handle + * this by providing a (small) stack of ErrorData records. The inner message + * can be computed and sent without disturbing the state of the outer message. + * (If the inner message is actually an error, this isn't very interesting + * because control won't come back to the outer message generator ... but + * if the inner message is only debug or log data, this is critical.) + * + * Second, actual recursion will occur if an error is reported by one of + * the elog.c routines or something they call. By far the most probable + * scenario of this sort is "out of memory"; and it's also the nastiest + * to handle because we'd likely also run out of memory while trying to + * report this error! Our escape hatch for this condition is to force any + * such messages up to ERROR level if they aren't already (so that we will + * not need to return to the outer elog.c call), and to reset the ErrorContext + * to empty before trying to process the inner message. Since ErrorContext + * is guaranteed to have at least 8K of space in it (see mcxt.c), we should + * be able to process an "out of memory" message successfully. + * * * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/utils/error/elog.c,v 1.108 2003/04/22 00:08:07 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/utils/error/elog.c,v 1.109 2003/04/24 21:16:44 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -25,17 +54,22 @@ #include #endif -#include "commands/copy.h" #include "libpq/libpq.h" #include "libpq/pqformat.h" +#include "mb/pg_wchar.h" #include "miscadmin.h" #include "storage/ipc.h" -#include "storage/proc.h" #include "tcop/tcopprot.h" #include "utils/memutils.h" #include "utils/guc.h" -#include "mb/pg_wchar.h" + +/* Global variables */ +ErrorContextCallback *error_context_stack = NULL; + +/* GUC parameters */ +bool Log_timestamp; /* show timestamps in stderr output */ +bool Log_pid; /* show PIDs in stderr output */ #ifdef HAVE_SYSLOG /* @@ -45,108 +79,118 @@ * ... in theory anyway */ int Use_syslog = 0; -char *Syslog_facility; +char *Syslog_facility; /* openlog() parameters */ char *Syslog_ident; static void write_syslog(int level, const char *line); #else + #define Use_syslog 0 -#endif -bool Log_timestamp; -bool Log_pid; +#endif /* HAVE_SYSLOG */ -#define TIMESTAMP_SIZE 20 /* format `YYYY-MM-DD HH:MM:SS ' */ -#define PID_SIZE 9 /* format `[123456] ' */ +/* + * ErrorData holds the data accumulated during any one ereport() cycle. + * Any non-NULL pointers must point to palloc'd data in ErrorContext. + * (The const pointers are an exception; we assume they point at non-freeable + * constant strings.) + */ + +typedef struct ErrorData +{ + int elevel; /* error level */ + bool output_to_server; /* will report to server log? */ + bool output_to_client; /* will report to client? */ + bool show_funcname; /* true to force funcname inclusion */ + const char *filename; /* __FILE__ of ereport() call */ + int lineno; /* __LINE__ of ereport() call */ + const char *funcname; /* __func__ of ereport() call */ + int sqlerrcode; /* encoded ERRSTATE */ + char *message; /* primary error message */ + char *detail; /* detail error message */ + char *hint; /* hint message */ + char *context; /* context message */ + int cursorpos; /* cursor index into query string */ + int saved_errno; /* errno at entry */ +} ErrorData; + +/* We provide a small stack of ErrorData records for re-entrant cases */ +#define ERRORDATA_STACK_SIZE 5 + +static ErrorData errordata[ERRORDATA_STACK_SIZE]; + +static int errordata_stack_depth = -1; /* index of topmost active frame */ + +static int recursion_depth = 0; /* to detect actual recursion */ + + +/* Macro for checking errordata_stack_depth is reasonable */ +#define CHECK_STACK_DEPTH() \ + do { \ + if (errordata_stack_depth < 0) \ + { \ + errordata_stack_depth = -1; \ + ereport(ERROR, (errmsg_internal("errstart was not called"))); \ + } \ + } while (0) + + +static void send_message_to_server_log(ErrorData *edata); +static void send_message_to_frontend(ErrorData *edata); +static char *expand_fmt_string(const char *fmt, ErrorData *edata); +static const char *useful_strerror(int errnum); +static const char *error_severity(int elevel); static const char *print_timestamp(void); static const char *print_pid(void); -static void send_message_to_frontend(int type, const char *msg); -static const char *useful_strerror(int errnum); -static const char *elog_message_prefix(int lev); - -static int Debugfile = -1; -/*-------------------- - * elog - * Primary error logging function. +/* + * errstart --- begin an error-reporting cycle * - * 'lev': error level; indicates recovery action to take, if any. - * 'fmt': a printf-style string. - * Additional arguments, if any, are formatted per %-escapes in 'fmt'. + * Create a stack entry and store the given parameters in it. Subsequently, + * errmsg() and perhaps other routines will be called to further populate + * the stack entry. Finally, errfinish() will be called to actually process + * the error report. * - * In addition to the usual %-escapes recognized by printf, "%m" in - * fmt is replaced by the error message for the current value of errno. - * - * Note: no newline is needed at the end of the fmt string, since - * elog will provide one for the output methods that need it. - * - * If 'lev' is ERROR or worse, control does not return to the caller. - * See elog.h for the error level definitions. - *-------------------- + * Returns TRUE in normal case. Returns FALSE to short-circuit the error + * report (if it's a warning or lower and not to be reported anywhere). */ -void -elog(int lev, const char *fmt,...) +bool +errstart(int elevel, const char *filename, int lineno, + const char *funcname) { - va_list ap; - - /* - * The expanded format and final output message are dynamically - * allocated if necessary, but not if they fit in the "reasonable - * size" buffers shown here. In extremis, we'd rather depend on - * having a few hundred bytes of stack space than on malloc() still - * working (since memory-clobber errors often take out malloc first). - * Don't make these buffers unreasonably large though, on pain of - * having to chase a bug with no error message. - * - * Note that we use malloc() not palloc() because we want to retain - * control if we run out of memory. palloc() would recursively call - * elog(ERROR), which would be all right except if we are working on a - * FATAL or PANIC error. We'd lose track of the fatal condition and - * report a mere ERROR to outer loop, which would be a Bad Thing. So, - * we substitute an appropriate message in-place, without downgrading - * the level if it's above ERROR. - */ - char fmt_fixedbuf[128]; - char msg_fixedbuf[256]; - char *fmt_buf = fmt_fixedbuf; - char *msg_buf = msg_fixedbuf; - char copylineno_buf[32]; /* for COPY line numbers */ - const char *errorstr; - const char *prefix; - const char *cp; - char *bp; - size_t space_needed; - size_t timestamp_size; /* prefix len for timestamp+pid */ + ErrorData *edata; bool output_to_server = false; bool output_to_client = false; - /* Check for old elog calls. Codes were renumbered in 7.3. 2002-02-24 */ - if (lev < DEBUG5) - elog(FATAL, "Pre-7.3 object file made an elog() call. Recompile."); + /* + * First decide whether we need to process this report at all; + * if it's warning or less and not enabled for logging, just + * return FALSE without starting up any error logging machinery. + */ /* * Convert initialization errors into fatal errors. This is probably * redundant, because Warn_restart_ready won't be set anyway. */ - if (lev == ERROR && IsInitProcessingMode()) - lev = FATAL; + if (elevel == ERROR && IsInitProcessingMode()) + elevel = FATAL; /* * If we are inside a critical section, all errors become PANIC * errors. See miscadmin.h. */ - if (lev >= ERROR) + if (elevel >= ERROR) { if (CritSectionCount > 0) - lev = PANIC; + elevel = PANIC; } /* Determine whether message is enabled for server log output */ /* Complicated because LOG is sorted out-of-order for this purpose */ - if (lev == LOG || lev == COMMERROR) + if (elevel == LOG || elevel == COMMERROR) { if (log_min_messages == LOG) output_to_server = true; @@ -155,19 +199,19 @@ elog(int lev, const char *fmt,...) } else { - /* lev != LOG */ + /* elevel != LOG */ if (log_min_messages == LOG) { - if (lev >= FATAL) + if (elevel >= FATAL) output_to_server = true; } /* Neither is LOG */ - else if (lev >= log_min_messages) + else if (elevel >= log_min_messages) output_to_server = true; } /* Determine whether message is enabled for client output */ - if (whereToSendOutput == Remote && lev != COMMERROR) + if (whereToSendOutput == Remote && elevel != COMMERROR) { /* * client_min_messages is honored only after we complete the @@ -176,271 +220,173 @@ elog(int lev, const char *fmt,...) * during authentication. */ if (ClientAuthInProgress) - output_to_client = (lev >= ERROR); + output_to_client = (elevel >= ERROR); else - output_to_client = (lev >= client_min_messages || lev == INFO); + output_to_client = (elevel >= client_min_messages || + elevel == INFO); } - /* Skip formatting effort if non-error message will not be output */ - if (lev < ERROR && !output_to_server && !output_to_client) - return; - - /* Save error str before calling any function that might change errno */ - errorstr = useful_strerror(errno); - - /* Internationalize the error format string */ - fmt = gettext(fmt); - - /* Begin formatting by determining prefix information */ - prefix = elog_message_prefix(lev); - - timestamp_size = 0; - if (Log_timestamp) - timestamp_size += TIMESTAMP_SIZE; - if (Log_pid) - timestamp_size += PID_SIZE; + /* Skip processing effort if non-error message will not be output */ + if (elevel < ERROR && !output_to_server && !output_to_client) + return false; /* - * Set up the expanded format, consisting of the prefix string plus - * input format, with any %m replaced by strerror() string (since - * vsnprintf won't know what to do with %m). To keep space - * calculation simple, we only allow one %m. + * Okay, crank up a stack entry to store the info in. */ - space_needed = timestamp_size + strlen(prefix) + - strlen(fmt) + strlen(errorstr) + 1; - if (copy_lineno) + if (recursion_depth++ > 0) { /* - * Prints the failure line of the COPY. Wow, what a hack! bjm - * Translator: Error message will be truncated at 31 characters. + * Ooops, error during error processing. Clear ErrorContext and force + * level up to ERROR or greater, as discussed at top of file. Adjust + * output decisions too. */ - snprintf(copylineno_buf, sizeof(copylineno_buf), - gettext("copy: line %d, "), copy_lineno); - space_needed += strlen(copylineno_buf); - } - - if (space_needed > sizeof(fmt_fixedbuf)) - { - fmt_buf = malloc(space_needed); - if (fmt_buf == NULL) - { - /* We're up against it, convert to out-of-memory error */ - fmt_buf = fmt_fixedbuf; - if (lev < ERROR) - { - lev = ERROR; - prefix = elog_message_prefix(lev); - } - - /* - * gettext doesn't allocate memory, except in the very first - * call (which this isn't), so it's safe to translate here. - * Worst case we get the untranslated string back. - */ - /* translator: This must fit in fmt_fixedbuf. */ - fmt = gettext("elog: out of memory"); - } - } - - fmt_buf[0] = '\0'; - - if (Log_timestamp) - strcat(fmt_buf, print_timestamp()); - if (Log_pid) - strcat(fmt_buf, print_pid()); - - strcat(fmt_buf, prefix); - - /* If error was in CopyFrom() print the offending line number -- dz */ - if (copy_lineno) - { - strcat(fmt_buf, copylineno_buf); - if (lev >= ERROR) - copy_lineno = 0; - } - - bp = fmt_buf + strlen(fmt_buf); - - for (cp = fmt; *cp; cp++) - { - if (cp[0] == '%' && cp[1] != '\0') - { - if (cp[1] == 'm') - { - /* - * XXX If there are any %'s in errorstr then vsnprintf - * will do the Wrong Thing; do we need to cope? Seems - * unlikely that % would appear in system errors. - */ - strcpy(bp, errorstr); - - /* - * copy the rest of fmt literally, since we can't afford - * to insert another %m. - */ - strcat(bp, cp + 2); - bp += strlen(bp); - break; - } - else - { - /* copy % and next char --- this avoids trouble with %%m */ - *bp++ = *cp++; - *bp++ = *cp; - } - } - else - *bp++ = *cp; - } - *bp = '\0'; - - /* - * Now generate the actual output text using vsnprintf(). Be sure to - * leave space for \n added later as well as trailing null. - */ - space_needed = sizeof(msg_fixedbuf); - for (;;) - { - int nprinted; - - va_start(ap, fmt); - nprinted = vsnprintf(msg_buf, space_needed - 2, fmt_buf, ap); - va_end(ap); - + MemoryContextReset(ErrorContext); + output_to_server = true; + if (whereToSendOutput == Remote && elevel != COMMERROR) + output_to_client = true; + elevel = Max(elevel, ERROR); /* - * Note: some versions of vsnprintf return the number of chars - * actually stored, but at least one returns -1 on failure. Be - * conservative about believing whether the print worked. + * If we recurse more than once, the problem might be something + * broken in a context traceback routine. Abandon them too. */ - if (nprinted >= 0 && nprinted < space_needed - 3) - break; - /* It didn't work, try to get a bigger buffer */ - if (msg_buf != msg_fixedbuf) - free(msg_buf); - space_needed *= 2; - msg_buf = malloc(space_needed); - if (msg_buf == NULL) - { - /* We're up against it, convert to out-of-memory error */ - msg_buf = msg_fixedbuf; - if (lev < ERROR) - { - lev = ERROR; - prefix = elog_message_prefix(lev); - } - msg_buf[0] = '\0'; - if (Log_timestamp) - strcat(msg_buf, print_timestamp()); - if (Log_pid) - strcat(msg_buf, print_pid()); - strcat(msg_buf, prefix); - strcat(msg_buf, gettext("elog: out of memory")); - break; - } + if (recursion_depth > 2) + error_context_stack = NULL; + } + if (++errordata_stack_depth >= ERRORDATA_STACK_SIZE) + { + /* Wups, stack not big enough */ + int i; + + elevel = Max(elevel, ERROR); + /* + * Don't forget any FATAL/PANIC status on the stack (see comments + * in errfinish) + */ + for (i = 0; i < errordata_stack_depth; i++) + elevel = Max(elevel, errordata[i].elevel); + /* Clear the stack and try again */ + errordata_stack_depth = -1; + ereport(elevel, (errmsg_internal("ERRORDATA_STACK_SIZE exceeded"))); } + /* Initialize data for this error frame */ + edata = &errordata[errordata_stack_depth]; + MemSet(edata, 0, sizeof(ErrorData)); + edata->elevel = elevel; + edata->output_to_server = output_to_server; + edata->output_to_client = output_to_client; + edata->filename = filename; + edata->lineno = lineno; + edata->funcname = funcname; + edata->sqlerrcode = ERRCODE_INTERNAL_ERROR; /* default errcode */ + /* errno is saved here so that error parameter eval can't change it */ + edata->saved_errno = errno; + + recursion_depth--; + return true; +} + +/* + * errfinish --- end an error-reporting cycle + * + * Produce the appropriate error report(s) and pop the error stack. + * + * If elevel is ERROR or worse, control does not return to the caller. + * See elog.h for the error level definitions. + */ +void +errfinish(int dummy, ...) +{ + ErrorData *edata = &errordata[errordata_stack_depth]; + int elevel = edata->elevel; + MemoryContext oldcontext; + ErrorContextCallback *econtext; + + CHECK_STACK_DEPTH(); /* - * Message prepared; send it where it should go + * Call any context callback functions. We can treat ereports occuring + * in callback functions as re-entrant rather than recursive case, so + * don't increment recursion_depth yet. */ - -#ifdef HAVE_SYSLOG - /* Write to syslog, if enabled */ - if (output_to_server && Use_syslog >= 1) + for (econtext = error_context_stack; + econtext != NULL; + econtext = econtext->previous) { - int syslog_level; - - switch (lev) - { - case DEBUG5: - case DEBUG4: - case DEBUG3: - case DEBUG2: - case DEBUG1: - syslog_level = LOG_DEBUG; - break; - case LOG: - case COMMERROR: - case INFO: - syslog_level = LOG_INFO; - break; - case NOTICE: - case WARNING: - syslog_level = LOG_NOTICE; - break; - case ERROR: - syslog_level = LOG_WARNING; - break; - case FATAL: - syslog_level = LOG_ERR; - break; - case PANIC: - default: - syslog_level = LOG_CRIT; - break; - } - - write_syslog(syslog_level, msg_buf + timestamp_size); + (*econtext->callback) (econtext->arg); } -#endif /* HAVE_SYSLOG */ - /* syslog doesn't want a trailing newline, but other destinations do */ - strcat(msg_buf, "\n"); + /* Now we are ready to process the error. */ + recursion_depth++; - /* Write to stderr, if enabled */ - if (output_to_server && (Use_syslog <= 1 || whereToSendOutput == Debug)) - write(2, msg_buf, strlen(msg_buf)); + /* + * Do processing in ErrorContext, which we hope has enough reserved space + * to report an error. + */ + oldcontext = MemoryContextSwitchTo(ErrorContext); + + /* Send to server log, if enabled */ + if (edata->output_to_server) + send_message_to_server_log(edata); + + /* + * Abort any old-style COPY OUT in progress when an error is detected. + * This hack is necessary because of poor design of old-style copy + * protocol. Note we must do this even if client is fool enough to + * have set client_min_messages above ERROR, so don't look at + * output_to_client. + */ + if (elevel >= ERROR && whereToSendOutput == Remote) + pq_endcopyout(true); /* Send to client, if enabled */ - if (output_to_client) + if (edata->output_to_client) + send_message_to_frontend(edata); + + /* Now free up subsidiary data attached to stack entry, and release it */ + if (edata->message) + pfree(edata->message); + if (edata->detail) + pfree(edata->detail); + if (edata->hint) + pfree(edata->hint); + if (edata->context) + pfree(edata->context); + + MemoryContextSwitchTo(oldcontext); + + errordata_stack_depth--; + recursion_depth--; + + /* + * If the error level is ERROR or more, we are not going to return to + * caller; therefore, if there is any stacked error already in progress + * it will be lost. This is more or less okay, except we do not want + * to have a FATAL or PANIC error downgraded because the reporting process + * was interrupted by a lower-grade error. So check the stack and make + * sure we panic if panic is warranted. + */ + if (elevel >= ERROR) { - /* Send IPC message to the front-end program */ - MemoryContext oldcxt; + int i; + + for (i = 0; i <= errordata_stack_depth; i++) + elevel = Max(elevel, errordata[i].elevel); /* - * Since backend libpq may call palloc(), switch to a context - * where there's fairly likely to be some free space. After all - * the pushups above, we don't want to drop the ball by running - * out of space now... + * Also, be sure to reset the stack to empty. We do not clear + * ErrorContext here, though; PostgresMain does that later on. */ - oldcxt = MemoryContextSwitchTo(ErrorContext); - - if (lev >= ERROR) - { - /* - * Abort any COPY OUT in progress when an error is detected. - * This hack is necessary because of poor design of old-style - * copy protocol. - */ - pq_endcopyout(true); - } - - /* Exclude the timestamp from msg sent to frontend */ - send_message_to_frontend(lev, msg_buf + timestamp_size); - - MemoryContextSwitchTo(oldcxt); + errordata_stack_depth = -1; + recursion_depth = 0; + error_context_stack = NULL; } - /* done with the message, release space */ - if (fmt_buf != fmt_fixedbuf) - free(fmt_buf); - if (msg_buf != msg_fixedbuf) - free(msg_buf); - /* - * If the user wants this elog() generating query logged, do so. We - * only want to log if the query has been written to - * debug_query_string. Also, avoid infinite loops. + * Perform error recovery action as specified by elevel. */ - - if (lev != LOG && lev >= log_min_error_statement && debug_query_string) - elog(LOG, "statement: %s", debug_query_string); - - /* - * Perform error recovery action as specified by lev. - */ - if (lev == ERROR || lev == FATAL) + if (elevel == ERROR || elevel == FATAL) { /* Prevent immediate interrupt while entering error recovery */ ImmediateInterruptOK = false; @@ -467,7 +413,7 @@ elog(int lev, const char *fmt,...) * postmaster (for example, if we are being run from the initdb * script, we'd better return an error status). */ - if (lev == FATAL || !Warn_restart_ready || proc_exit_inprogress) + if (elevel == FATAL || !Warn_restart_ready || proc_exit_inprogress) { /* * fflush here is just to improve the odds that we get to see @@ -482,10 +428,10 @@ elog(int lev, const char *fmt,...) } /* - * Guard against infinite loop from elog() during error recovery. + * Guard against infinite loop from errors during error recovery. */ if (InError) - elog(PANIC, "elog: error during error recovery, giving up!"); + ereport(PANIC, (errmsg("error during error recovery, giving up"))); InError = true; /* @@ -494,7 +440,7 @@ elog(int lev, const char *fmt,...) siglongjmp(Warn_restart, 1); } - if (lev == PANIC) + if (elevel >= PANIC) { /* * Serious crash time. Postmaster will observe nonzero process @@ -509,18 +455,281 @@ elog(int lev, const char *fmt,...) abort(); } - /* We reach here if lev <= WARNING. OK to return to caller. */ + /* We reach here if elevel <= WARNING. OK to return to caller. */ } +/* + * errcode --- add SQLSTATE error code to the current error + * + * The code is expected to be represented as per MAKE_SQLSTATE(). + */ int +errcode(int sqlerrcode) +{ + ErrorData *edata = &errordata[errordata_stack_depth]; + + /* we don't bother incrementing recursion_depth */ + CHECK_STACK_DEPTH(); + + edata->sqlerrcode = sqlerrcode; + + return 0; /* return value does not matter */ +} + + +/* + * This macro handles expansion of a format string and associated parameters; + * it's common code for errmsg(), errdetail(), etc. Must be called inside + * a routine that is declared like "const char *fmt, ..." and has an edata + * pointer set up. The message is assigned to edata->targetfield, or + * appended to it if appendval is true. + * + * Note: we pstrdup the buffer rather than just transferring its storage + * to the edata field because the buffer might be considerably larger than + * really necessary. + */ +#define EVALUATE_MESSAGE(targetfield, appendval) \ + { \ + char *fmtbuf; \ + StringInfoData buf; \ + /* Internationalize the error format string */ \ + fmt = gettext(fmt); \ + /* Expand %m in format string */ \ + fmtbuf = expand_fmt_string(fmt, edata); \ + initStringInfo(&buf); \ + if ((appendval) && edata->targetfield) \ + appendStringInfo(&buf, "%s\n", edata->targetfield); \ + /* Generate actual output --- have to use appendStringInfoVA */ \ + for (;;) \ + { \ + va_list args; \ + bool success; \ + va_start(args, fmt); \ + success = appendStringInfoVA(&buf, fmtbuf, args); \ + va_end(args); \ + if (success) \ + break; \ + enlargeStringInfo(&buf, buf.maxlen); \ + } \ + /* Done with expanded fmt */ \ + pfree(fmtbuf); \ + /* Save the completed message into the stack item */ \ + if (edata->targetfield) \ + pfree(edata->targetfield); \ + edata->targetfield = pstrdup(buf.data); \ + pfree(buf.data); \ + } + + +/* + * errmsg --- add a primary error message text to the current error + * + * In addition to the usual %-escapes recognized by printf, "%m" in + * fmt is replaced by the error message for the caller's value of errno. + * + * Note: no newline is needed at the end of the fmt string, since + * ereport will provide one for the output methods that need it. + */ +int +errmsg(const char *fmt, ...) +{ + ErrorData *edata = &errordata[errordata_stack_depth]; + MemoryContext oldcontext; + + recursion_depth++; + CHECK_STACK_DEPTH(); + oldcontext = MemoryContextSwitchTo(ErrorContext); + + EVALUATE_MESSAGE(message, false); + + MemoryContextSwitchTo(oldcontext); + recursion_depth--; + return 0; /* return value does not matter */ +} + + +/* + * errmsg_internal --- add a primary error message text to the current error + * + * This is exactly like errmsg() except that strings passed to errmsg_internal + * are customarily left out of the internationalization message dictionary. + * This should be used for "can't happen" cases that are probably not worth + * spending translation effort on. + */ +int +errmsg_internal(const char *fmt, ...) +{ + ErrorData *edata = &errordata[errordata_stack_depth]; + MemoryContext oldcontext; + + recursion_depth++; + CHECK_STACK_DEPTH(); + oldcontext = MemoryContextSwitchTo(ErrorContext); + + EVALUATE_MESSAGE(message, false); + + MemoryContextSwitchTo(oldcontext); + recursion_depth--; + return 0; /* return value does not matter */ +} + + +/* + * errdetail --- add a detail error message text to the current error + */ +int +errdetail(const char *fmt, ...) +{ + ErrorData *edata = &errordata[errordata_stack_depth]; + MemoryContext oldcontext; + + recursion_depth++; + CHECK_STACK_DEPTH(); + oldcontext = MemoryContextSwitchTo(ErrorContext); + + EVALUATE_MESSAGE(detail, false); + + MemoryContextSwitchTo(oldcontext); + recursion_depth--; + return 0; /* return value does not matter */ +} + + +/* + * errhint --- add a hint error message text to the current error + */ +int +errhint(const char *fmt, ...) +{ + ErrorData *edata = &errordata[errordata_stack_depth]; + MemoryContext oldcontext; + + recursion_depth++; + CHECK_STACK_DEPTH(); + oldcontext = MemoryContextSwitchTo(ErrorContext); + + EVALUATE_MESSAGE(hint, false); + + MemoryContextSwitchTo(oldcontext); + recursion_depth--; + return 0; /* return value does not matter */ +} + + +/* + * errcontext --- add a context error message text to the current error + * + * Unlike other cases, multiple calls are allowed to build up a stack of + * context information. We assume earlier calls represent more-closely-nested + * states. + */ +int +errcontext(const char *fmt, ...) +{ + ErrorData *edata = &errordata[errordata_stack_depth]; + MemoryContext oldcontext; + + recursion_depth++; + CHECK_STACK_DEPTH(); + oldcontext = MemoryContextSwitchTo(ErrorContext); + + EVALUATE_MESSAGE(context, true); + + MemoryContextSwitchTo(oldcontext); + recursion_depth--; + return 0; /* return value does not matter */ +} + + +/* + * errfunction --- add reporting function name to the current error + * + * This is used when backwards compatibility demands that the function + * name appear in messages sent to old-protocol clients. Note that the + * passed string is expected to be a non-freeable constant string. + */ +int +errfunction(const char *funcname) +{ + ErrorData *edata = &errordata[errordata_stack_depth]; + + /* we don't bother incrementing recursion_depth */ + CHECK_STACK_DEPTH(); + + edata->funcname = funcname; + edata->show_funcname = true; + + return 0; /* return value does not matter */ +} + +/* + * errposition --- add cursor position to the current error + */ +int +errposition(int cursorpos) +{ + ErrorData *edata = &errordata[errordata_stack_depth]; + + /* we don't bother incrementing recursion_depth */ + CHECK_STACK_DEPTH(); + + edata->cursorpos = cursorpos; + + return 0; /* return value does not matter */ +} + + +/* + * elog_finish --- finish up for old-style API + * + * The elog() macro already called errstart, but with ERROR rather than + * the true elevel. + */ +void +elog_finish(int elevel, const char *fmt, ...) +{ + ErrorData *edata = &errordata[errordata_stack_depth]; + MemoryContext oldcontext; + + CHECK_STACK_DEPTH(); + + /* + * We need to redo errstart() because the elog macro had to call it + * with bogus elevel. + */ + errordata_stack_depth--; + errno = edata->saved_errno; + if (!errstart(elevel, edata->filename, edata->lineno, edata->funcname)) + return; /* nothing to do */ + + /* + * Format error message just like errmsg(). + */ + recursion_depth++; + oldcontext = MemoryContextSwitchTo(ErrorContext); + + EVALUATE_MESSAGE(message, false); + + MemoryContextSwitchTo(oldcontext); + recursion_depth--; + + /* + * And let errfinish() finish up. + */ + errfinish(0); +} + + +/* + * Initialization of error output file + */ +void DebugFileOpen(void) { int fd, istty; - Debugfile = -1; - if (OutputFileName[0]) { /* @@ -530,8 +739,8 @@ DebugFileOpen(void) */ if ((fd = open(OutputFileName, O_CREAT | O_APPEND | O_WRONLY, 0666)) < 0) - elog(FATAL, "DebugFileOpen: open of %s: %m", - OutputFileName); + ereport(FATAL, + (errmsg("failed to open %s: %m", OutputFileName))); istty = isatty(fd); close(fd); @@ -539,9 +748,9 @@ DebugFileOpen(void) * Redirect our stderr to the debug output file. */ if (!freopen(OutputFileName, "a", stderr)) - elog(FATAL, "DebugFileOpen: %s reopen as stderr: %m", - OutputFileName); - Debugfile = fileno(stderr); + ereport(FATAL, + (errmsg("failed to reopen %s as stderr: %m", + OutputFileName))); /* * If the file is a tty and we're running under the postmaster, @@ -551,66 +760,10 @@ DebugFileOpen(void) */ if (istty && IsUnderPostmaster) if (!freopen(OutputFileName, "a", stdout)) - elog(FATAL, "DebugFileOpen: %s reopen as stdout: %m", - OutputFileName); - return Debugfile; + ereport(FATAL, + (errmsg("failed to reopen %s as stdout: %m", + OutputFileName))); } - - /* - * If no filename was specified, send debugging output to stderr. If - * stderr has been hosed, try to open a file. - */ - fd = fileno(stderr); - if (fcntl(fd, F_GETFD, 0) < 0) - { - snprintf(OutputFileName, MAXPGPATH, "%s/pg.errors.%d", - DataDir, (int) MyProcPid); - fd = open(OutputFileName, O_CREAT | O_APPEND | O_WRONLY, 0666); - } - if (fd < 0) - elog(FATAL, "DebugFileOpen: could not open debugging file"); - - Debugfile = fd; - return Debugfile; -} - - -/* - * Return a timestamp string like - * - * "2000-06-04 13:12:03 " - */ -static const char * -print_timestamp(void) -{ - time_t curtime; - static char buf[TIMESTAMP_SIZE + 1]; - - curtime = time(NULL); - - strftime(buf, sizeof(buf), - "%Y-%m-%d %H:%M:%S ", - localtime(&curtime)); - - return buf; -} - - - -/* - * Return a string like - * - * "[123456] " - * - * with the current pid. - */ -static const char * -print_pid(void) -{ - static char buf[PID_SIZE + 1]; - - snprintf(buf, PID_SIZE + 1, "[%d] ", (int) MyProcPid); - return buf; } @@ -679,7 +832,6 @@ write_syslog(int level, const char *line) { char buf[PG_SYSLOG_LIMIT + 1]; int buflen; - int l; int i; /* if we start at a newline, move ahead one char */ @@ -695,28 +847,24 @@ write_syslog(int level, const char *line) if (strchr(buf, '\n') != NULL) *strchr(buf, '\n') = '\0'; - l = strlen(buf); + buflen = strlen(buf); /* trim to multibyte letter boundary */ - buflen = pg_mbcliplen(buf, l, l); + buflen = pg_mbcliplen(buf, buflen, buflen); if (buflen <= 0) return; buf[buflen] = '\0'; - l = strlen(buf); /* already word boundary? */ - if (isspace((unsigned char) line[l]) || line[l] == '\0') - buflen = l; - else + if (!isspace((unsigned char) line[buflen]) && + line[buflen] != '\0') { /* try to divide at word boundary */ - i = l - 1; + i = buflen - 1; while (i > 0 && !isspace((unsigned char) buf[i])) i--; - if (i <= 0) /* couldn't divide word boundary */ - buflen = l; - else + if (i > 0) /* else couldn't divide word boundary */ { buflen = i; buf[i] = '\0'; @@ -736,19 +884,222 @@ write_syslog(int level, const char *line) syslog(level, "[%lu] %s", seq, line); } } + #endif /* HAVE_SYSLOG */ +/* + * Write error report to server's log + */ static void -send_message_to_frontend(int type, const char *msg) +send_message_to_server_log(ErrorData *edata) { - StringInfoData buf; + StringInfoData buf; + + initStringInfo(&buf); + + appendStringInfo(&buf, "%s: ", error_severity(edata->elevel)); + + if (edata->message) + appendStringInfo(&buf, "%s\n", edata->message); + else + appendStringInfoString(&buf, "missing error text\n"); + + /* XXX showing of additional info should perhaps be optional */ + /* XXX ought to localize the label strings, probably */ + + if (edata->detail) + appendStringInfo(&buf, "DETAIL: %s\n", edata->detail); + if (edata->hint) + appendStringInfo(&buf, "HINT: %s\n", edata->hint); + if (edata->context) + appendStringInfo(&buf, "CONTEXT: %s\n", edata->context); + if (edata->funcname && edata->filename) + appendStringInfo(&buf, "IN: %s (%s:%d)\n", + edata->funcname, edata->filename, edata->lineno); + + /* + * If the user wants the query that generated this error logged, do so. + * We use debug_query_string to get at the query, which is kinda useless + * for queries triggered by extended query protocol; how to improve? + */ + if (edata->elevel >= log_min_error_statement && debug_query_string != NULL) + appendStringInfo(&buf, "STATEMENT: %s\n", debug_query_string); + + +#ifdef HAVE_SYSLOG + /* Write to syslog, if enabled */ + if (Use_syslog >= 1) + { + int syslog_level; + + switch (edata->elevel) + { + case DEBUG5: + case DEBUG4: + case DEBUG3: + case DEBUG2: + case DEBUG1: + syslog_level = LOG_DEBUG; + break; + case LOG: + case COMMERROR: + case INFO: + syslog_level = LOG_INFO; + break; + case NOTICE: + case WARNING: + syslog_level = LOG_NOTICE; + break; + case ERROR: + syslog_level = LOG_WARNING; + break; + case FATAL: + syslog_level = LOG_ERR; + break; + case PANIC: + default: + syslog_level = LOG_CRIT; + break; + } + + write_syslog(syslog_level, buf.data); + } +#endif /* HAVE_SYSLOG */ + + /* Write to stderr, if enabled */ + if (Use_syslog <= 1 || whereToSendOutput == Debug) + { + /* + * Timestamp and PID are only used for stderr output --- we assume + * the syslog daemon will supply them for us in the other case. + */ + if (Log_timestamp) + fprintf(stderr, "%s", print_timestamp()); + if (Log_pid) + fprintf(stderr, "%s", print_pid()); + fprintf(stderr, "%s", buf.data); + } + + pfree(buf.data); +} + + +/* + * Write error report to client + */ +static void +send_message_to_frontend(ErrorData *edata) +{ + StringInfoData msgbuf; /* 'N' (Notice) is for nonfatal conditions, 'E' is for errors */ - pq_beginmessage(&buf, (type < ERROR) ? 'N' : 'E'); - /* XXX more to do here */ - pq_sendstring(&buf, msg); - pq_endmessage(&buf); + pq_beginmessage(&msgbuf, (edata->elevel < ERROR) ? 'N' : 'E'); + + if (PG_PROTOCOL_MAJOR(FrontendProtocol) >= 3) + { + /* New style with separate fields */ + char tbuf[12]; + int ssval; + int i; + + pq_sendbyte(&msgbuf, 'S'); + pq_sendstring(&msgbuf, error_severity(edata->elevel)); + + /* unpack MAKE_SQLSTATE code */ + ssval = edata->sqlerrcode; + for (i = 0; i < 5; i++) + { + tbuf[i] = PGUNSIXBIT(ssval); + ssval >>= 6; + } + tbuf[i] = '\0'; + + pq_sendbyte(&msgbuf, 'C'); + pq_sendstring(&msgbuf, tbuf); + + /* M field is required per protocol, so always send something */ + pq_sendbyte(&msgbuf, 'M'); + if (edata->message) + pq_sendstring(&msgbuf, edata->message); + else + pq_sendstring(&msgbuf, gettext("missing error text")); + + if (edata->detail) + { + pq_sendbyte(&msgbuf, 'D'); + pq_sendstring(&msgbuf, edata->detail); + } + + if (edata->hint) + { + pq_sendbyte(&msgbuf, 'H'); + pq_sendstring(&msgbuf, edata->hint); + } + + if (edata->context) + { + pq_sendbyte(&msgbuf, 'W'); + pq_sendstring(&msgbuf, edata->context); + } + + if (edata->cursorpos > 0) + { + snprintf(tbuf, sizeof(tbuf), "%d", edata->cursorpos); + pq_sendbyte(&msgbuf, 'P'); + pq_sendstring(&msgbuf, tbuf); + } + + if (edata->filename) + { + pq_sendbyte(&msgbuf, 'F'); + pq_sendstring(&msgbuf, edata->filename); + } + + if (edata->lineno > 0) + { + snprintf(tbuf, sizeof(tbuf), "%d", edata->lineno); + pq_sendbyte(&msgbuf, 'L'); + pq_sendstring(&msgbuf, tbuf); + } + + if (edata->funcname) + { + pq_sendbyte(&msgbuf, 'R'); + pq_sendstring(&msgbuf, edata->funcname); + } + + pq_sendbyte(&msgbuf, '\0'); /* terminator */ + } + else + { + /* Old style --- gin up a backwards-compatible message */ + StringInfoData buf; + + initStringInfo(&buf); + + appendStringInfo(&buf, "%s: ", error_severity(edata->elevel)); + + if (edata->show_funcname && edata->funcname) + appendStringInfo(&buf, "%s: ", edata->funcname); + + if (edata->message) + appendStringInfo(&buf, "%s", edata->message); + else + appendStringInfoString(&buf, gettext("missing error text")); + + if (edata->cursorpos > 0) + appendStringInfo(&buf, gettext(" at character %d"), + edata->cursorpos); + + appendStringInfoChar(&buf, '\n'); + + pq_sendstring(&msgbuf, buf.data); + + pfree(buf.data); + } + + pq_endmessage(&msgbuf); /* * This flush is normally not necessary, since postgres.c will flush @@ -762,12 +1113,73 @@ send_message_to_frontend(int type, const char *msg) } +/* + * Support routines for formatting error messages. + */ + + +/* + * expand_fmt_string --- process special format codes in a format string + * + * We must replace %m with the appropriate strerror string, since vsnprintf + * won't know what to do with it. + * + * The result is a palloc'd string. + */ +static char * +expand_fmt_string(const char *fmt, ErrorData *edata) +{ + StringInfoData buf; + const char *cp; + + initStringInfo(&buf); + + for (cp = fmt; *cp; cp++) + { + if (cp[0] == '%' && cp[1] != '\0') + { + cp++; + if (*cp == 'm') + { + /* + * Replace %m by system error string. If there are any %'s + * in the string, we'd better double them so that vsnprintf + * won't misinterpret. + */ + const char *cp2; + + cp2 = useful_strerror(edata->saved_errno); + for (; *cp2; cp2++) + { + if (*cp2 == '%') + appendStringInfoCharMacro(&buf, '%'); + appendStringInfoCharMacro(&buf, *cp2); + } + } + else + { + /* copy % and next char --- this avoids trouble with %%m */ + appendStringInfoCharMacro(&buf, '%'); + appendStringInfoCharMacro(&buf, *cp); + } + } + else + appendStringInfoCharMacro(&buf, *cp); + } + + return buf.data; +} + + +/* + * A slightly cleaned-up version of strerror() + */ static const char * useful_strerror(int errnum) { /* this buffer is only used if errno has a bogus value */ static char errorstr_buf[48]; - char *str; + const char *str; if (errnum == ERANGE) /* small trick to save creating many regression test result files */ @@ -794,45 +1206,87 @@ useful_strerror(int errnum) } - +/* + * error_severity --- get localized string representing elevel + */ static const char * -elog_message_prefix(int lev) +error_severity(int elevel) { - const char *prefix = NULL; + const char *prefix; - switch (lev) + switch (elevel) { case DEBUG1: case DEBUG2: case DEBUG3: case DEBUG4: case DEBUG5: - prefix = gettext("DEBUG: "); + prefix = gettext("DEBUG"); break; case LOG: case COMMERROR: - prefix = gettext("LOG: "); + prefix = gettext("LOG"); break; case INFO: - prefix = gettext("INFO: "); + prefix = gettext("INFO"); break; case NOTICE: - prefix = gettext("NOTICE: "); + prefix = gettext("NOTICE"); break; case WARNING: - prefix = gettext("WARNING: "); + prefix = gettext("WARNING"); break; case ERROR: - prefix = gettext("ERROR: "); + prefix = gettext("ERROR"); break; case FATAL: - prefix = gettext("FATAL: "); + prefix = gettext("FATAL"); break; case PANIC: - prefix = gettext("PANIC: "); + prefix = gettext("PANIC"); + break; + default: + prefix = "???"; break; } - Assert(prefix != NULL); return prefix; } + + +/* + * Return a timestamp string like + * + * "2000-06-04 13:12:03 " + */ +static const char * +print_timestamp(void) +{ + time_t curtime; + static char buf[21]; /* format `YYYY-MM-DD HH:MM:SS ' */ + + curtime = time(NULL); + + strftime(buf, sizeof(buf), + "%Y-%m-%d %H:%M:%S ", + localtime(&curtime)); + + return buf; +} + + +/* + * Return a string like + * + * "[123456] " + * + * with the current pid. + */ +static const char * +print_pid(void) +{ + static char buf[10]; /* allow `[123456] ' */ + + snprintf(buf, sizeof(buf), "[%d] ", (int) MyProcPid); + return buf; +} diff --git a/src/include/commands/copy.h b/src/include/commands/copy.h index dcfce2f804..4eab57362a 100644 --- a/src/include/commands/copy.h +++ b/src/include/commands/copy.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $Id: copy.h,v 1.20 2002/09/04 20:31:42 momjian Exp $ + * $Id: copy.h,v 1.21 2003/04/24 21:16:44 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -15,10 +15,8 @@ #define COPY_H #include "nodes/parsenodes.h" -#include "nodes/primnodes.h" -extern int copy_lineno; -void DoCopy(const CopyStmt *stmt); +extern void DoCopy(const CopyStmt *stmt); #endif /* COPY_H */ diff --git a/src/include/lib/stringinfo.h b/src/include/lib/stringinfo.h index c49a73c0eb..8e305ee430 100644 --- a/src/include/lib/stringinfo.h +++ b/src/include/lib/stringinfo.h @@ -10,7 +10,7 @@ * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $Id: stringinfo.h,v 1.25 2003/04/19 00:02:29 tgl Exp $ + * $Id: stringinfo.h,v 1.26 2003/04/24 21:16:44 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -80,16 +80,32 @@ extern void initStringInfo(StringInfo str); /*------------------------ * appendStringInfo - * Format text data under the control of fmt (an sprintf-like format string) + * Format text data under the control of fmt (an sprintf-style format string) * and append it to whatever is already in str. More space is allocated * to str if necessary. This is sort of like a combination of sprintf and * strcat. */ -extern void -appendStringInfo(StringInfo str, const char *fmt,...) +extern void appendStringInfo(StringInfo str, const char *fmt, ...) /* This extension allows gcc to check the format string */ __attribute__((format(printf, 2, 3))); +/*------------------------ + * appendStringInfoVA + * Attempt to format text data under the control of fmt (an sprintf-style + * format string) and append it to whatever is already in str. If successful + * return true; if not (because there's not enough space), return false + * without modifying str. Typically the caller would enlarge str and retry + * on false return --- see appendStringInfo for standard usage pattern. + */ +extern bool appendStringInfoVA(StringInfo str, const char *fmt, va_list args); + +/*------------------------ + * appendStringInfoString + * Append a null-terminated string to str. + * Like appendStringInfo(str, "%s", s) but faster. + */ +extern void appendStringInfoString(StringInfo str, const char *s); + /*------------------------ * appendStringInfoChar * Append a single byte to str. diff --git a/src/include/libpq/pqcomm.h b/src/include/libpq/pqcomm.h index 420f1e438e..4f458c51c2 100644 --- a/src/include/libpq/pqcomm.h +++ b/src/include/libpq/pqcomm.h @@ -9,7 +9,7 @@ * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $Id: pqcomm.h,v 1.78 2003/04/22 00:08:07 tgl Exp $ + * $Id: pqcomm.h,v 1.79 2003/04/24 21:16:44 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -106,7 +106,7 @@ typedef union SockAddr /* The earliest and latest frontend/backend protocol version supported. */ #define PG_PROTOCOL_EARLIEST PG_PROTOCOL(1,0) -#define PG_PROTOCOL_LATEST PG_PROTOCOL(3,102) /* XXX temporary value */ +#define PG_PROTOCOL_LATEST PG_PROTOCOL(3,103) /* XXX temporary value */ typedef uint32 ProtocolVersion; /* FE/BE protocol version number */ diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in index 33a20e3cb9..b54b6f9d2a 100644 --- a/src/include/pg_config.h.in +++ b/src/include/pg_config.h.in @@ -109,6 +109,12 @@ /* Define to 1 if fseeko (and presumably ftello) exists and is declared. */ #undef HAVE_FSEEKO +/* Define to 1 if your compiler understands __func__. */ +#undef HAVE_FUNCNAME__FUNC + +/* Define to 1 if your compiler understands __FUNCTION__. */ +#undef HAVE_FUNCNAME__FUNCTION + /* Define to 1 if you have the `getaddrinfo' function. */ #undef HAVE_GETADDRINFO diff --git a/src/include/utils/elog.h b/src/include/utils/elog.h index b1f431ed19..e341fa48f3 100644 --- a/src/include/utils/elog.h +++ b/src/include/utils/elog.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $Id: elog.h,v 1.40 2002/09/04 20:31:45 momjian Exp $ + * $Id: elog.h,v 1.41 2003/04/24 21:16:44 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -40,19 +40,233 @@ /*#define DEBUG DEBUG1*/ /* Backward compatibility with pre-7.3 */ -/* Configurable parameters */ -#ifdef HAVE_SYSLOG -extern int Use_syslog; + +/* macros for representing SQLSTATE strings compactly */ +#define PGSIXBIT(ch) (((ch) - '0') & 0x3F) +#define PGUNSIXBIT(val) (((val) & 0x3F) + '0') + +#define MAKE_SQLSTATE(ch1,ch2,ch3,ch4,ch5) \ + (PGSIXBIT(ch1) + (PGSIXBIT(ch2) << 6) + (PGSIXBIT(ch3) << 12) + \ + (PGSIXBIT(ch4) << 18) + (PGSIXBIT(ch5) << 24)) + + +/* SQLSTATE codes defined by SQL99 */ +#define ERRCODE_AMBIGUOUS_CURSOR_NAME MAKE_SQLSTATE('3','C', '0','0','0') +#define ERRCODE_CARDINALITY_VIOLATION MAKE_SQLSTATE('2','1', '0','0','0') +#define ERRCODE_CLI_SPECIFIC_CONDITION MAKE_SQLSTATE('H','Y', '0','0','0') +#define ERRCODE_CONNECTION_EXCEPTION MAKE_SQLSTATE('0','8', '0','0','0') +#define ERRCODE_CONNECTION_DOES_NOT_EXIST MAKE_SQLSTATE('0','8', '0','0','3') +#define ERRCODE_CONNECTION_FAILURE MAKE_SQLSTATE('0','8', '0','0','6') +#define ERRCODE_CONNECTION_NAME_IN_USE MAKE_SQLSTATE('0','8', '0','0','2') +#define ERRCODE_SQLCLIENT_UNABLE_TO_ESTABLISH_SQLCONNECTION MAKE_SQLSTATE('0','8', '0','0','1') +#define ERRCODE_SQLSERVER_REJECTED_ESTABLISHMENT_OF_SQLCONNECTION MAKE_SQLSTATE('0','8', '0','0','4') +#define ERRCODE_TRANSACTION_RESOLUTION_UNKNOWN MAKE_SQLSTATE('0','8', '0','0','7') +#define ERRCODE_DATA_EXCEPTION MAKE_SQLSTATE('2','2', '0','0','0') +#define ERRCODE_ARRAY_ELEMENT_ERROR MAKE_SQLSTATE('2','2', '0','2','E') +#define ERRCODE_CHARACTER_NOT_IN_REPERTOIRE MAKE_SQLSTATE('2','2', '0','2','1') +#define ERRCODE_DATETIME_FIELD_OVERFLOW MAKE_SQLSTATE('2','2', '0','0','8') +#define ERRCODE_DIVISION_BY_ZERO MAKE_SQLSTATE('2','2', '0','1','2') +#define ERRCODE_ERROR_IN_ASSIGNMENT MAKE_SQLSTATE('2','2', '0','0','5') +#define ERRCODE_ESCAPE_CHARACTER_CONFLICT MAKE_SQLSTATE('2','2', '0','0','B') +#define ERRCODE_INDICATOR_OVERFLOW MAKE_SQLSTATE('2','2', '0','2','2') +#define ERRCODE_INTERVAL_FIELD_OVERFLOW MAKE_SQLSTATE('2','2', '0','1','5') +#define ERRCODE_INVALID_CHARACTER_VALUE_FOR_CAST MAKE_SQLSTATE('2','2', '0','1','8') +#define ERRCODE_INVALID_DATETIME_FORMAT MAKE_SQLSTATE('2','2', '0','0','7') +#define ERRCODE_INVALID_ESCAPE_CHARACTER MAKE_SQLSTATE('2','2', '0','1','9') +#define ERRCODE_INVALID_ESCAPE_OCTET MAKE_SQLSTATE('2','2', '0','0','D') +#define ERRCODE_INVALID_ESCAPE_SEQUENCE MAKE_SQLSTATE('2','2', '0','2','5') +#define ERRCODE_INVALID_INDICATOR_PARAMETER_VALUE MAKE_SQLSTATE('2','2', '0','1','0') +#define ERRCODE_INVALID_LIMIT_VALUE MAKE_SQLSTATE('2','2', '0','2','0') +#define ERRCODE_INVALID_PARAMETER_VALUE MAKE_SQLSTATE('2','2', '0','2','3') +#define ERRCODE_INVALID_REGULAR_EXPRESSION MAKE_SQLSTATE('2','2', '0','1','B') +#define ERRCODE_INVALID_TIME_ZONE_DISPLACEMENT_VALUE MAKE_SQLSTATE('2','2', '0','0','9') +#define ERRCODE_INVALID_USE_OF_ESCAPE_CHARACTER MAKE_SQLSTATE('2','2', '0','0','C') +#define ERRCODE_NULL_VALUE_NO_INDICATOR_PARAMETER MAKE_SQLSTATE('2','2', '0','0','G') +#define ERRCODE_MOST_SPECIFIC_TYPE_MISMATCH MAKE_SQLSTATE('2','2', '0','0','2') +#define ERRCODE_NULL_VALUE_NOT_ALLOWED MAKE_SQLSTATE('2','2', '0','0','4') +#define ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE MAKE_SQLSTATE('2','2', '0','0','3') +#define ERRCODE_STRING_DATA_LENGTH_MISMATCH MAKE_SQLSTATE('2','2', '0','2','6') +#define ERRCODE_STRING_DATA_RIGHT_TRUNCATION MAKE_SQLSTATE('2','2', '0','0','1') +#define ERRCODE_SUBSTRING_ERROR MAKE_SQLSTATE('2','2', '0','1','1') +#define ERRCODE_TRIM_ERROR MAKE_SQLSTATE('2','2', '0','2','7') +#define ERRCODE_UNTERMINATED_C_STRING MAKE_SQLSTATE('2','2', '0','2','4') +#define ERRCODE_ZERO_LENGTH_CHARACTER_STRING MAKE_SQLSTATE('2','2', '0','0','F') +#define ERRCODE_DEPENDENT_PRIVILEGE_DESCRIPTORS_STILL_EXIST MAKE_SQLSTATE('2','B', '0','0','0') +#define ERRCODE_EXTERNAL_ROUTINE_EXCEPTION MAKE_SQLSTATE('3','8', '0','0','0') +#define ERRCODE_EXTERNAL_ROUTINE_EXCEPTION_CONTAINING_SQL_NOT_PERMITTED MAKE_SQLSTATE('3','8', '0','0','1') +#define ERRCODE_EXTERNAL_ROUTINE_EXCEPTION_MODIFYING_SQL_DATA_NOT_PERMITTED MAKE_SQLSTATE('3','8', '0','0','2') +#define ERRCODE_EXTERNAL_ROUTINE_EXCEPTION_PROHIBITED_SQL_STATEMENT_ATTEMPTED MAKE_SQLSTATE('3','8', '0','0','3') +#define ERRCODE_EXTERNAL_ROUTINE_EXCEPTION_READING_SQL_DATA_NOT_PERMITTED MAKE_SQLSTATE('3','8', '0','0','4') +#define ERRCODE_EXTERNAL_ROUTINE_INVOCATION_EXCEPTION MAKE_SQLSTATE('3','9', '0','0','0') +#define ERRCODE_EXTERNAL_ROUTINE_INVOCATION_EXCEPTION_INVALID_SQLSTATE_RETURNED MAKE_SQLSTATE('3','9', '0','0','1') +#define ERRCODE_EXTERNAL_ROUTINE_INVOCATION_EXCEPTION_NULL_VALUE_NOT_ALLOWED MAKE_SQLSTATE('3','9', '0','0','4') +#define ERRCODE_FEATURE_NOT_SUPPORTED MAKE_SQLSTATE('0','A', '0','0','0') +#define ERRCODE_MULTIPLE_ENVIRONMENT_TRANSACTIONS MAKE_SQLSTATE('0','A', '0','0','1') +#define ERRCODE_INTEGRITY_CONSTRAINT_VIOLATION MAKE_SQLSTATE('2','3', '0','0','0') +#define ERRCODE_RESTRICT_VIOLATION MAKE_SQLSTATE('2','3', '0','0','1') +#define ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION MAKE_SQLSTATE('2','8', '0','0','0') +#define ERRCODE_INVALID_CATALOG_NAME MAKE_SQLSTATE('3','D', '0','0','0') +#define ERRCODE_INVALID_CONDITION_NUMBER MAKE_SQLSTATE('3','5', '0','0','0') +#define ERRCODE_INVALID_CONNECTION_NAME MAKE_SQLSTATE('2','E', '0','0','0') +#define ERRCODE_INVALID_CURSOR_NAME MAKE_SQLSTATE('3','4', '0','0','0') +#define ERRCODE_INVALID_CURSOR_STATE MAKE_SQLSTATE('2','4', '0','0','0') +#define ERRCODE_INVALID_GRANTOR_STATE MAKE_SQLSTATE('0','L', '0','0','0') +#define ERRCODE_INVALID_ROLE_SPECIFICATION MAKE_SQLSTATE('0','P', '0','0','0') +#define ERRCODE_INVALID_SCHEMA_NAME MAKE_SQLSTATE('3','F', '0','0','0') +#define ERRCODE_INVALID_SQL_DESCRIPTOR_NAME MAKE_SQLSTATE('3','3', '0','0','0') +#define ERRCODE_INVALID_SQL_STATEMENT MAKE_SQLSTATE('3','0', '0','0','0') +#define ERRCODE_INVALID_SQL_STATEMENT_NAME MAKE_SQLSTATE('2','6', '0','0','0') +#define ERRCODE_INVALID_TARGET_SPECIFICATION_VALUE MAKE_SQLSTATE('3','1', '0','0','0') +#define ERRCODE_INVALID_TRANSACTION_STATE MAKE_SQLSTATE('2','5', '0','0','0') +#define ERRCODE_ACTIVE_SQL_TRANSACTION MAKE_SQLSTATE('2','5', '0','0','1') +#define ERRCODE_BRANCH_TRANSACTION_ALREADY_ACTIVE MAKE_SQLSTATE('2','5', '0','0','2') +#define ERRCODE_HELD_CURSOR_REQUIRES_SAME_ISOLATION_LEVEL MAKE_SQLSTATE('2','5', '0','0','8') +#define ERRCODE_INAPPROPRIATE_ACCESS_MODE_FOR_BRANCH_TRANSACTION MAKE_SQLSTATE('2','5', '0','0','3') +#define ERRCODE_INAPPROPRIATE_ISOLATION_LEVEL_FOR_BRANCH_TRANSACTION MAKE_SQLSTATE('2','5', '0','0','4') +#define ERRCODE_NO_ACTIVE_SQL_TRANSACTION_FOR_BRANCH_TRANSACTION MAKE_SQLSTATE('2','5', '0','0','5') +#define ERRCODE_READ_ONLY_SQL_TRANSACTION MAKE_SQLSTATE('2','5', '0','0','6') +#define ERRCODE_SCHEMA_AND_DATA_STATEMENT_MIXING_NOT_SUPPORTED MAKE_SQLSTATE('2','5', '0','0','7') +#define ERRCODE_INVALID_TRANSACTION_INITIATION MAKE_SQLSTATE('0','B', '0','0','0') +#define ERRCODE_INVALID_TRANSACTION_TERMINATION MAKE_SQLSTATE('2','D', '0','0','0') +#define ERRCODE_LOCATOR_EXCEPTION MAKE_SQLSTATE('0','F', '0','0','0') +#define ERRCODE_LOCATOR_EXCEPTION_INVALID_SPECIFICATION MAKE_SQLSTATE('0','F', '0','0','1') +#define ERRCODE_NO_DATA MAKE_SQLSTATE('0','2', '0','0','0') +#define ERRCODE_NO_ADDITIONAL_DYNAMIC_RESULT_SETS_RETURNED MAKE_SQLSTATE('0','2', '0','0','1') +#define ERRCODE_REMOTE_DATABASE_ACCESS MAKE_SQLSTATE('H','Z', '0','0','0') +#define ERRCODE_SAVEPOINT_EXCEPTION MAKE_SQLSTATE('3','B', '0','0','0') +#define ERRCODE_SAVEPOINT_EXCEPTION_INVALID_SPECIFICATION MAKE_SQLSTATE('3','B', '0','0','1') +#define ERRCODE_SAVEPOINT_EXCEPTION_TOO_MANY MAKE_SQLSTATE('3','B', '0','0','2') +#define ERRCODE_SQL_ROUTINE_EXCEPTION MAKE_SQLSTATE('2','F', '0','0','0') +#define ERRCODE_FUNCTION_EXECUTED_NO_RETURN_STATEMENT MAKE_SQLSTATE('2','F', '0','0','5') +#define ERRCODE_MODIFYING_SQL_DATA_NOT_PERMITTED MAKE_SQLSTATE('2','F', '0','0','2') +#define ERRCODE_PROHIBITED_SQL_STATEMENT_ATTEMPTED MAKE_SQLSTATE('2','F', '0','0','3') +#define ERRCODE_READING_SQL_DATA_NOT_PERMITTED MAKE_SQLSTATE('2','F', '0','0','4') +#define ERRCODE_SQL_STATEMENT_NOT_YET_COMPLETE MAKE_SQLSTATE('0','3', '0','0','0') +#define ERRCODE_SUCCESSFUL_COMPLETION MAKE_SQLSTATE('0','0', '0','0','0') +#define ERRCODE_SYNTAX_ERROR_OR_ACCESS_RULE_VIOLATION MAKE_SQLSTATE('4','2', '0','0','0') +#define ERRCODE_SYNTAX_ERROR_OR_ACCESS_RULE_VIOLATION_IN_DIRECT_STATEMENT MAKE_SQLSTATE('2','A', '0','0','0') +#define ERRCODE_SYNTAX_ERROR_OR_ACCESS_RULE_VIOLATION_IN_DYNAMIC_STATEMENT MAKE_SQLSTATE('3','7', '0','0','0') +#define ERRCODE_TRANSACTION_ROLLBACK MAKE_SQLSTATE('4','0', '0','0','0') +#define ERRCODE_TRANSACTION_ROLLBACK_INTEGRITY_CONSTRAINT_VIOLATION MAKE_SQLSTATE('4','0', '0','0','2') +#define ERRCODE_TRANSACTION_ROLLBACK_SERIALIZATION_FAILURE MAKE_SQLSTATE('4','0', '0','0','1') +#define ERRCODE_TRANSACTION_ROLLBACK_STATEMENT_COMPLETION_UNKNOWN MAKE_SQLSTATE('4','0', '0','0','3') +#define ERRCODE_TRIGGERED_DATA_CHANGE_VIOLATION MAKE_SQLSTATE('2','7', '0','0','0') +#define ERRCODE_WARNING MAKE_SQLSTATE('0','1', '0','0','0') +#define ERRCODE_CURSOR_OPERATION_CONFLICT MAKE_SQLSTATE('0','1', '0','0','1') +#define ERRCODE_DISCONNECT_ERROR MAKE_SQLSTATE('0','1', '0','0','2') +#define ERRCODE_DYNAMIC_RESULT_SETS_RETURNED MAKE_SQLSTATE('0','1', '0','0','C') +#define ERRCODE_IMPLICIT_ZERO_BIT_PADDING MAKE_SQLSTATE('0','1', '0','0','8') +#define ERRCODE_NULL_VALUE_ELIMINATED_IN_SET_FUNCTION MAKE_SQLSTATE('0','1', '0','0','3') +#define ERRCODE_PRIVILEGE_NOT_GRANTED MAKE_SQLSTATE('0','1', '0','0','7') +#define ERRCODE_PRIVILEGE_NOT_REVOKED MAKE_SQLSTATE('0','1', '0','0','6') +#define ERRCODE_QUERY_EXPRESSION_TOO_LONG_FOR_INFORMATION_SCHEMA MAKE_SQLSTATE('0','1', '0','0','A') +#define ERRCODE_SEARCH_CONDITION_TOO_LONG_FOR_INFORMATION_SCHEMA MAKE_SQLSTATE('0','1', '0','0','9') +#define ERRCODE_STATEMENT_TOO_LONG_FOR_INFORMATION_SCHEMA MAKE_SQLSTATE('0','1', '0','0','5') +#define ERRCODE_STRING_DATA_RIGHT_TRUNCATION_WARNING MAKE_SQLSTATE('0','1', '0','0','4') +#define ERRCODE_WITH_CHECK_OPTION_VIOLATION MAKE_SQLSTATE('4','4', '0','0','0') + +/* Implementation-defined error codes for PostgreSQL */ +#define ERRCODE_INTERNAL_ERROR MAKE_SQLSTATE('X','X', '0','0','0') + + +/* Which __func__ symbol do we have, if any? */ +#ifdef HAVE_FUNCNAME__FUNC +#define PG_FUNCNAME_MACRO __func__ +#else +#ifdef HAVE_FUNCNAME__FUNCTION +#define PG_FUNCNAME_MACRO __FUNCTION__ +#else +#define PG_FUNCNAME_MACRO NULL #endif -extern bool Log_timestamp; -extern bool Log_pid; +#endif + + +/*---------- + * New-style error reporting API: to be used in this way: + * ereport(ERROR, + * (errcode(ERRCODE_INVALID_CURSOR_NAME), + * errmsg("portal \"%s\" not found", stmt->portalname), + * ... other errxxx() fields as needed ...)); + * + * The error level is required, and so is a primary error message (errmsg + * or errmsg_internal). All else is optional. errcode() defaults to + * ERRCODE_INTERNAL_ERROR. + *---------- + */ +#define ereport(elevel, rest) \ + (errstart(elevel, __FILE__, __LINE__, PG_FUNCNAME_MACRO) ? \ + (errfinish rest) : (void) 0) + +extern bool errstart(int elevel, const char *filename, int lineno, + const char *funcname); +extern void errfinish(int dummy, ...); + +extern int errcode(int sqlerrcode); + +extern int errmsg(const char *fmt, ...) +/* This extension allows gcc to check the format string for consistency with + the supplied arguments. */ +__attribute__((format(printf, 1, 2))); + +extern int errmsg_internal(const char *fmt, ...) +/* This extension allows gcc to check the format string for consistency with + the supplied arguments. */ +__attribute__((format(printf, 1, 2))); + +extern int errdetail(const char *fmt, ...) +/* This extension allows gcc to check the format string for consistency with + the supplied arguments. */ +__attribute__((format(printf, 1, 2))); + +extern int errhint(const char *fmt, ...) +/* This extension allows gcc to check the format string for consistency with + the supplied arguments. */ +__attribute__((format(printf, 1, 2))); + +extern int errcontext(const char *fmt, ...) +/* This extension allows gcc to check the format string for consistency with + the supplied arguments. */ +__attribute__((format(printf, 1, 2))); + +extern int errfunction(const char *funcname); +extern int errposition(int cursorpos); + + +/*---------- + * Old-style error reporting API: to be used in this way: + * elog(ERROR, "portal \"%s\" not found", stmt->portalname); + *---------- + */ +#define elog errstart(ERROR, __FILE__, __LINE__, PG_FUNCNAME_MACRO), elog_finish extern void -elog(int lev, const char *fmt,...) +elog_finish(int elevel, const char *fmt, ...) /* This extension allows gcc to check the format string for consistency with the supplied arguments. */ __attribute__((format(printf, 2, 3))); -extern int DebugFileOpen(void); + +/* Support for attaching context information to error reports */ + +typedef struct ErrorContextCallback +{ + struct ErrorContextCallback *previous; + void (*callback) (void *arg); + void *arg; +} ErrorContextCallback; + +extern ErrorContextCallback *error_context_stack; + + +/* GUC-configurable parameters */ +extern bool Log_timestamp; +extern bool Log_pid; +#ifdef HAVE_SYSLOG +extern int Use_syslog; +#endif + + +/* Other exported functions */ +extern void DebugFileOpen(void); #endif /* ELOG_H */ diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index 086462094f..6ee2716ec0 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/interfaces/libpq/fe-connect.c,v 1.234 2003/04/22 00:08:07 tgl Exp $ + * $Header: /cvsroot/pgsql/src/interfaces/libpq/fe-connect.c,v 1.235 2003/04/24 21:16:44 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -1429,20 +1429,13 @@ keep_going: /* We will come back to here until there /* Handle errors. */ if (beresp == 'E') { - if (pqGets(&conn->errorMessage, conn)) + if (pqGetErrorNotice(conn, true)) { /* We'll come back when there is more data */ return PGRES_POLLING_READING; } /* OK, we read the message; mark data consumed */ conn->inStart = conn->inCursor; - - /* - * The postmaster typically won't end its message with - * a newline, so add one to conform to libpq - * conventions. - */ - appendPQExpBufferChar(&conn->errorMessage, '\n'); goto error_return; } diff --git a/src/interfaces/libpq/fe-exec.c b/src/interfaces/libpq/fe-exec.c index 16e63f7f68..3fe0ddc492 100644 --- a/src/interfaces/libpq/fe-exec.c +++ b/src/interfaces/libpq/fe-exec.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/interfaces/libpq/fe-exec.c,v 1.130 2003/04/22 00:08:07 tgl Exp $ + * $Header: /cvsroot/pgsql/src/interfaces/libpq/fe-exec.c,v 1.131 2003/04/24 21:16:44 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -55,7 +55,6 @@ static void handleSyncLoss(PGconn *conn, char id, int msgLength); static int getRowDescriptions(PGconn *conn); static int getAnotherTuple(PGconn *conn, int binary); static int getNotify(PGconn *conn); -static int getNotice(PGconn *conn); /* --------------- * Escaping arbitrary strings to get valid SQL strings/identifiers. @@ -390,6 +389,16 @@ PQmakeEmptyPGresult(PGconn *conn, ExecStatusType status) result->cmdStatus[0] = '\0'; result->binary = 0; result->errMsg = NULL; + result->errSeverity = NULL; + result->errCode = NULL; + result->errPrimary = NULL; + result->errDetail = NULL; + result->errHint = NULL; + result->errPosition = NULL; + result->errContext = NULL; + result->errFilename = NULL; + result->errLineno = NULL; + result->errFuncname = NULL; result->null_field[0] = '\0'; result->curBlock = NULL; result->curOffset = 0; @@ -949,7 +958,7 @@ parseInput(PGconn *conn) } else if (id == 'N') { - if (getNotice(conn)) + if (pqGetErrorNotice(conn, false)) return; } else if (conn->asyncStatus != PGASYNC_BUSY) @@ -968,7 +977,7 @@ parseInput(PGconn *conn) */ if (id == 'E') { - if (getNotice(conn)) + if (pqGetErrorNotice(conn, false /* treat as notice */)) return; } else @@ -999,10 +1008,8 @@ parseInput(PGconn *conn) conn->asyncStatus = PGASYNC_READY; break; case 'E': /* error return */ - if (pqGets(&conn->errorMessage, conn)) + if (pqGetErrorNotice(conn, true)) return; - /* build an error result holding the error message */ - saveErrorResult(conn); conn->asyncStatus = PGASYNC_READY; break; case 'Z': /* backend is ready for new query */ @@ -1540,31 +1547,132 @@ errout: /* - * Attempt to read a Notice response message. + * Attempt to read an Error or Notice response message. * This is possible in several places, so we break it out as a subroutine. - * Entry: 'N' message type and length have already been consumed. - * Exit: returns 0 if successfully consumed Notice message. + * Entry: 'E' or 'N' message type and length have already been consumed. + * Exit: returns 0 if successfully consumed message. * returns EOF if not enough data. */ -static int -getNotice(PGconn *conn) +int +pqGetErrorNotice(PGconn *conn, bool isError) { - /* - * Since the Notice might be pretty long, we create a temporary - * PQExpBuffer rather than using conn->workBuffer. workBuffer is - * intended for stuff that is expected to be short. - */ - PQExpBufferData noticeBuf; + PGresult *res; + PQExpBufferData workBuf; + char id; - initPQExpBuffer(¬iceBuf); - if (pqGets(¬iceBuf, conn)) + /* + * Make a PGresult to hold the accumulated fields. We temporarily + * lie about the result status, so that PQmakeEmptyPGresult doesn't + * uselessly copy conn->errorMessage. + */ + res = PQmakeEmptyPGresult(conn, PGRES_EMPTY_QUERY); + res->resultStatus = PGRES_FATAL_ERROR; + /* + * Since the fields might be pretty long, we create a temporary + * PQExpBuffer rather than using conn->workBuffer. workBuffer is + * intended for stuff that is expected to be short. We shouldn't + * use conn->errorMessage either, since this might be only a notice. + */ + initPQExpBuffer(&workBuf); + + /* + * Read the fields and save into res. + */ + for (;;) { - termPQExpBuffer(¬iceBuf); - return EOF; + if (pqGetc(&id, conn)) + goto fail; + if (id == '\0') + break; /* terminator found */ + if (pqGets(&workBuf, conn)) + goto fail; + switch (id) + { + case 'S': + res->errSeverity = pqResultStrdup(res, workBuf.data); + break; + case 'C': + res->errCode = pqResultStrdup(res, workBuf.data); + break; + case 'M': + res->errPrimary = pqResultStrdup(res, workBuf.data); + break; + case 'D': + res->errDetail = pqResultStrdup(res, workBuf.data); + break; + case 'H': + res->errHint = pqResultStrdup(res, workBuf.data); + break; + case 'P': + res->errPosition = pqResultStrdup(res, workBuf.data); + break; + case 'W': + res->errContext = pqResultStrdup(res, workBuf.data); + break; + case 'F': + res->errFilename = pqResultStrdup(res, workBuf.data); + break; + case 'L': + res->errLineno = pqResultStrdup(res, workBuf.data); + break; + case 'R': + res->errFuncname = pqResultStrdup(res, workBuf.data); + break; + default: + /* silently ignore any other field type */ + break; + } } - DONOTICE(conn, noticeBuf.data); - termPQExpBuffer(¬iceBuf); + + /* + * Now build the "overall" error message for PQresultErrorMessage. + * + * XXX this should be configurable somehow. + */ + resetPQExpBuffer(&workBuf); + if (res->errSeverity) + appendPQExpBuffer(&workBuf, "%s: ", res->errSeverity); + if (res->errPrimary) + appendPQExpBufferStr(&workBuf, res->errPrimary); + /* translator: %s represents a digit string */ + if (res->errPosition) + appendPQExpBuffer(&workBuf, libpq_gettext(" at character %s"), + res->errPosition); + appendPQExpBufferChar(&workBuf, '\n'); + if (res->errDetail) + appendPQExpBuffer(&workBuf, libpq_gettext("DETAIL: %s\n"), + res->errDetail); + if (res->errHint) + appendPQExpBuffer(&workBuf, libpq_gettext("HINT: %s\n"), + res->errHint); + if (res->errContext) + appendPQExpBuffer(&workBuf, libpq_gettext("CONTEXT: %s\n"), + res->errContext); + + /* + * Either save error as current async result, or just emit the notice. + */ + if (isError) + { + res->errMsg = pqResultStrdup(res, workBuf.data); + pqClearAsyncResult(conn); + conn->result = res; + resetPQExpBuffer(&conn->errorMessage); + appendPQExpBufferStr(&conn->errorMessage, workBuf.data); + } + else + { + DONOTICE(conn, workBuf.data); + PQclear(res); + } + + termPQExpBuffer(&workBuf); return 0; + +fail: + PQclear(res); + termPQExpBuffer(&workBuf); + return EOF; } /* @@ -2123,10 +2231,8 @@ PQfn(PGconn *conn, } break; case 'E': /* error return */ - if (pqGets(&conn->errorMessage, conn)) + if (pqGetErrorNotice(conn, true)) continue; - /* build an error result holding the error message */ - saveErrorResult(conn); status = PGRES_FATAL_ERROR; break; case 'A': /* notify message */ @@ -2136,7 +2242,7 @@ PQfn(PGconn *conn, break; case 'N': /* notice */ /* handle notice and go back to processing return values */ - if (getNotice(conn)) + if (pqGetErrorNotice(conn, false)) continue; break; case 'Z': /* backend is ready for new query */ diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 35e3208eb0..4688fbf8f5 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -12,7 +12,7 @@ * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $Id: libpq-int.h,v 1.63 2003/04/22 00:08:07 tgl Exp $ + * $Id: libpq-int.h,v 1.64 2003/04/24 21:16:44 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -56,7 +56,7 @@ typedef int ssize_t; /* ssize_t doesn't exist in VC (atleast * pqcomm.h describe what the backend knows, not what libpq knows. */ -#define PG_PROTOCOL_LIBPQ PG_PROTOCOL(3,102) /* XXX temporary value */ +#define PG_PROTOCOL_LIBPQ PG_PROTOCOL(3,103) /* XXX temporary value */ /* * POSTGRES backend dependent Constants. @@ -101,7 +101,7 @@ typedef struct pgresAttDesc /* We use char* for Attribute values. The value pointer always points to a null-terminated area; we add a null (zero) byte after whatever the backend sends us. This is only - particularly useful for ASCII tuples ... with a binary value, the + particularly useful for text tuples ... with a binary value, the value might have embedded nulls, so the application can't use C string operators on it. But we add a null anyway for consistency. Note that the value itself does not contain a length word. @@ -133,7 +133,7 @@ struct pg_result char cmdStatus[CMDSTATUS_LEN]; /* cmd status from the * last query */ int binary; /* binary tuple values if binary == 1, - * otherwise ASCII */ + * otherwise text */ /* * The conn link in PGresult is no longer used by any libpq code. It @@ -152,9 +152,25 @@ struct pg_result void *noticeArg; int client_encoding; /* encoding id */ - + /* + * Error information (all NULL if not an error result). errMsg is the + * "overall" error message returned by PQresultErrorMessage. If we + * got a field-ized error from the server then the additional fields + * may be set. + */ char *errMsg; /* error message, or NULL if no error */ + char *errSeverity; /* severity code */ + char *errCode; /* SQLSTATE code */ + char *errPrimary; /* primary message text */ + char *errDetail; /* detail text */ + char *errHint; /* hint text */ + char *errPosition; /* cursor position */ + char *errContext; /* location information */ + char *errFilename; /* source-code file name */ + char *errLineno; /* source-code line number */ + char *errFuncname; /* source-code function name */ + /* All NULL attributes in the query result point to this null string */ char null_field[1]; @@ -321,6 +337,7 @@ extern void pqSetResultError(PGresult *res, const char *msg); extern void *pqResultAlloc(PGresult *res, size_t nBytes, bool isBinary); extern char *pqResultStrdup(PGresult *res, const char *str); extern void pqClearAsyncResult(PGconn *conn); +extern int pqGetErrorNotice(PGconn *conn, bool isError); /* === in fe-misc.c === */ diff --git a/src/pl/plpgsql/src/pl_comp.c b/src/pl/plpgsql/src/pl_comp.c index dedbca9d2f..feb80f94ae 100644 --- a/src/pl/plpgsql/src/pl_comp.c +++ b/src/pl/plpgsql/src/pl_comp.c @@ -3,7 +3,7 @@ * procedural language * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/pl/plpgsql/src/pl_comp.c,v 1.55 2003/03/25 00:34:23 tgl Exp $ + * $Header: /cvsroot/pgsql/src/pl/plpgsql/src/pl_comp.c,v 1.56 2003/04/24 21:16:44 tgl Exp $ * * This software is copyrighted by Jan Wieck - Hamburg. * @@ -80,6 +80,7 @@ int plpgsql_DumpExecTree = 0; PLpgSQL_function *plpgsql_curr_compile; +static void plpgsql_compile_error_callback(void *arg); static PLpgSQL_row *build_rowtype(Oid classOid); @@ -121,7 +122,7 @@ plpgsql_compile(Oid fn_oid, int functype) PLpgSQL_rec *rec; int i; int arg_varnos[FUNC_MAX_ARGS]; - sigjmp_buf save_restart; + ErrorContextCallback plerrcontext; /* * Lookup the pg_proc tuple by Oid @@ -133,37 +134,25 @@ plpgsql_compile(Oid fn_oid, int functype) elog(ERROR, "plpgsql: cache lookup for proc %u failed", fn_oid); /* - * Setup the scanner input and error info + * Setup the scanner input and error info. We assume that this function + * cannot be invoked recursively, so there's no need to save and restore + * the static variables used here. */ procStruct = (Form_pg_proc) GETSTRUCT(procTup); proc_source = DatumGetCString(DirectFunctionCall1(textout, PointerGetDatum(&procStruct->prosrc))); plpgsql_setinput(proc_source, functype); + plpgsql_error_funcname = pstrdup(NameStr(procStruct->proname)); plpgsql_error_lineno = 0; /* - * Catch elog() so we can provide notice about where the error is + * Setup error traceback support for ereport() */ - memcpy(&save_restart, &Warn_restart, sizeof(save_restart)); - if (sigsetjmp(Warn_restart, 1) != 0) - { - memcpy(&Warn_restart, &save_restart, sizeof(Warn_restart)); - - /* - * If we are the first of cascaded error catchings, print where - * this happened - */ - if (plpgsql_error_funcname != NULL) - { - elog(WARNING, "plpgsql: ERROR during compile of %s near line %d", - plpgsql_error_funcname, plpgsql_error_lineno); - - plpgsql_error_funcname = NULL; - } - - siglongjmp(Warn_restart, 1); - } + plerrcontext.callback = plpgsql_compile_error_callback; + plerrcontext.arg = NULL; + plerrcontext.previous = error_context_stack; + error_context_stack = &plerrcontext; /* * Initialize the compiler @@ -530,11 +519,11 @@ plpgsql_compile(Oid fn_oid, int functype) ReleaseSysCache(procTup); /* - * Restore the previous elog() jump target + * Pop the error context stack */ + error_context_stack = plerrcontext.previous; plpgsql_error_funcname = NULL; plpgsql_error_lineno = 0; - memcpy(&Warn_restart, &save_restart, sizeof(Warn_restart)); /* * Finally return the compiled function @@ -545,6 +534,18 @@ plpgsql_compile(Oid fn_oid, int functype) } +/* + * error context callback to let us supply a call-stack traceback + */ +static void +plpgsql_compile_error_callback(void *arg) +{ + if (plpgsql_error_funcname) + errcontext("compile of PL/pgSQL function %s near line %d", + plpgsql_error_funcname, plpgsql_error_lineno); +} + + /* ---------- * plpgsql_parse_word The scanner calls this to postparse * any single word not found by a diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c index 6620e41970..efb423541a 100644 --- a/src/pl/plpgsql/src/pl_exec.c +++ b/src/pl/plpgsql/src/pl_exec.c @@ -3,7 +3,7 @@ * procedural language * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/pl/plpgsql/src/pl_exec.c,v 1.85 2003/04/08 23:20:04 tgl Exp $ + * $Header: /cvsroot/pgsql/src/pl/plpgsql/src/pl_exec.c,v 1.86 2003/04/24 21:16:44 tgl Exp $ * * This software is copyrighted by Jan Wieck - Hamburg. * @@ -55,14 +55,12 @@ #include "utils/syscache.h" -static PLpgSQL_function *error_info_func = NULL; -static PLpgSQL_stmt *error_info_stmt = NULL; -static char *error_info_text = NULL; - +static const char * const raise_skip_msg = "RAISE"; /************************************************************ * Local function forward declarations ************************************************************/ +static void plpgsql_exec_error_callback(void *arg); static PLpgSQL_var *copy_var(PLpgSQL_var * var); static PLpgSQL_rec *copy_rec(PLpgSQL_rec * rec); @@ -173,61 +171,26 @@ Datum plpgsql_exec_function(PLpgSQL_function * func, FunctionCallInfo fcinfo) { PLpgSQL_execstate estate; + ErrorContextCallback plerrcontext; int i; - sigjmp_buf save_restart; - PLpgSQL_function *save_efunc; - PLpgSQL_stmt *save_estmt; - char *save_etext; - - /* - * Setup debug error info and catch elog() - */ - save_efunc = error_info_func; - save_estmt = error_info_stmt; - save_etext = error_info_text; - - error_info_func = func; - error_info_stmt = NULL; - error_info_text = "while initialization of execution state"; - - memcpy(&save_restart, &Warn_restart, sizeof(save_restart)); - if (sigsetjmp(Warn_restart, 1) != 0) - { - memcpy(&Warn_restart, &save_restart, sizeof(Warn_restart)); - - /* - * If we are the first of cascaded error catchings, print where - * this happened - */ - if (error_info_func != NULL) - { - elog(WARNING, "Error occurred while executing PL/pgSQL function %s", - error_info_func->fn_name); - if (error_info_stmt != NULL) - elog(WARNING, "line %d at %s", error_info_stmt->lineno, - plpgsql_stmt_typename(error_info_stmt)); - else if (error_info_text != NULL) - elog(WARNING, "%s", error_info_text); - else - elog(WARNING, "no more error information available"); - - error_info_func = NULL; - error_info_stmt = NULL; - error_info_text = NULL; - } - - siglongjmp(Warn_restart, 1); - } - /* * Setup the execution state */ plpgsql_estate_setup(&estate, func, (ReturnSetInfo *) fcinfo->resultinfo); + /* + * Setup error traceback support for ereport() + */ + plerrcontext.callback = plpgsql_exec_error_callback; + plerrcontext.arg = &estate; + plerrcontext.previous = error_context_stack; + error_context_stack = &plerrcontext; + /* * Make local execution copies of all the datums */ + estate.err_text = "while initialization of execution state"; for (i = 0; i < func->ndatums; i++) { switch (func->datums[i]->dtype) @@ -257,7 +220,7 @@ plpgsql_exec_function(PLpgSQL_function * func, FunctionCallInfo fcinfo) /* * Put the actual call argument values into the variables */ - error_info_text = "while putting call arguments to local variables"; + estate.err_text = "while putting call arguments to local variables"; for (i = 0; i < func->fn_nargs; i++) { int n = func->fn_argvarnos[i]; @@ -298,7 +261,7 @@ plpgsql_exec_function(PLpgSQL_function * func, FunctionCallInfo fcinfo) * Initialize the other variables to NULL values for now. The default * values are set when the blocks are entered. */ - error_info_text = "while initializing local variables to NULL"; + estate.err_text = "while initializing local variables to NULL"; for (i = estate.found_varno; i < estate.ndatums; i++) { switch (estate.datums[i]->dtype) @@ -333,20 +296,20 @@ plpgsql_exec_function(PLpgSQL_function * func, FunctionCallInfo fcinfo) /* * Now call the toplevel block of statements */ - error_info_text = NULL; - error_info_stmt = (PLpgSQL_stmt *) (func->action); + estate.err_text = NULL; + estate.err_stmt = (PLpgSQL_stmt *) (func->action); if (exec_stmt_block(&estate, func->action) != PLPGSQL_RC_RETURN) { - error_info_stmt = NULL; - error_info_text = "at END of toplevel PL block"; + estate.err_stmt = NULL; + estate.err_text = "at END of toplevel PL block"; elog(ERROR, "control reaches end of function without RETURN"); } /* * We got a return value - process it */ - error_info_stmt = NULL; - error_info_text = "while casting return value to function's return type"; + estate.err_stmt = NULL; + estate.err_text = "while casting return value to function's return type"; fcinfo->isnull = estate.retisnull; @@ -417,12 +380,9 @@ plpgsql_exec_function(PLpgSQL_function * func, FunctionCallInfo fcinfo) exec_eval_cleanup(&estate); /* - * Restore the previous error info and elog() jump target + * Pop the error context stack */ - error_info_func = save_efunc; - error_info_stmt = save_estmt; - error_info_text = save_etext; - memcpy(&Warn_restart, &save_restart, sizeof(Warn_restart)); + error_context_stack = plerrcontext.previous; /* * Return the functions result @@ -441,65 +401,30 @@ plpgsql_exec_trigger(PLpgSQL_function * func, TriggerData *trigdata) { PLpgSQL_execstate estate; + ErrorContextCallback plerrcontext; int i; - sigjmp_buf save_restart; - PLpgSQL_function *save_efunc; - PLpgSQL_stmt *save_estmt; - char *save_etext; PLpgSQL_var *var; PLpgSQL_rec *rec_new, *rec_old; HeapTuple rettup; - /* - * Setup debug error info and catch elog() - */ - save_efunc = error_info_func; - save_estmt = error_info_stmt; - save_etext = error_info_text; - - error_info_func = func; - error_info_stmt = NULL; - error_info_text = "while initialization of execution state"; - - memcpy(&save_restart, &Warn_restart, sizeof(save_restart)); - if (sigsetjmp(Warn_restart, 1) != 0) - { - memcpy(&Warn_restart, &save_restart, sizeof(Warn_restart)); - - /* - * If we are the first of cascaded error catchings, print where - * this happened - */ - if (error_info_func != NULL) - { - elog(WARNING, "Error occurred while executing PL/pgSQL function %s", - error_info_func->fn_name); - if (error_info_stmt != NULL) - elog(WARNING, "line %d at %s", error_info_stmt->lineno, - plpgsql_stmt_typename(error_info_stmt)); - else if (error_info_text != NULL) - elog(WARNING, "%s", error_info_text); - else - elog(WARNING, "no more error information available"); - - error_info_func = NULL; - error_info_stmt = NULL; - error_info_text = NULL; - } - - siglongjmp(Warn_restart, 1); - } - - /* * Setup the execution state */ plpgsql_estate_setup(&estate, func, NULL); + /* + * Setup error traceback support for ereport() + */ + plerrcontext.callback = plpgsql_exec_error_callback; + plerrcontext.arg = &estate; + plerrcontext.previous = error_context_stack; + error_context_stack = &plerrcontext; + /* * Make local execution copies of all the datums */ + estate.err_text = "while initialization of execution state"; for (i = 0; i < func->ndatums; i++) { switch (func->datums[i]->dtype) @@ -634,7 +559,7 @@ plpgsql_exec_trigger(PLpgSQL_function * func, * Put the actual call argument values into the special execution * state variables */ - error_info_text = "while putting call arguments to local variables"; + estate.err_text = "while putting call arguments to local variables"; estate.trig_nargs = trigdata->tg_trigger->tgnargs; if (estate.trig_nargs == 0) estate.trig_argv = NULL; @@ -650,7 +575,7 @@ plpgsql_exec_trigger(PLpgSQL_function * func, * Initialize the other variables to NULL values for now. The default * values are set when the blocks are entered. */ - error_info_text = "while initializing local variables to NULL"; + estate.err_text = "while initializing local variables to NULL"; for (i = estate.found_varno; i < estate.ndatums; i++) { switch (estate.datums[i]->dtype) @@ -686,12 +611,12 @@ plpgsql_exec_trigger(PLpgSQL_function * func, /* * Now call the toplevel block of statements */ - error_info_text = NULL; - error_info_stmt = (PLpgSQL_stmt *) (func->action); + estate.err_text = NULL; + estate.err_stmt = (PLpgSQL_stmt *) (func->action); if (exec_stmt_block(&estate, func->action) != PLPGSQL_RC_RETURN) { - error_info_stmt = NULL; - error_info_text = "at END of toplevel PL block"; + estate.err_stmt = NULL; + estate.err_text = "at END of toplevel PL block"; elog(ERROR, "control reaches end of trigger procedure without RETURN"); } @@ -723,12 +648,9 @@ plpgsql_exec_trigger(PLpgSQL_function * func, exec_eval_cleanup(&estate); /* - * Restore the previous error info and elog() jump target + * Pop the error context stack */ - error_info_func = save_efunc; - error_info_stmt = save_estmt; - error_info_text = save_etext; - memcpy(&Warn_restart, &save_restart, sizeof(Warn_restart)); + error_context_stack = plerrcontext.previous; /* * Return the triggers result @@ -737,6 +659,37 @@ plpgsql_exec_trigger(PLpgSQL_function * func, } +/* + * error context callback to let us supply a call-stack traceback + */ +static void +plpgsql_exec_error_callback(void *arg) +{ + PLpgSQL_execstate *estate = (PLpgSQL_execstate *) arg; + + /* safety check, shouldn't happen */ + if (estate->err_func == NULL) + return; + + /* if we are doing RAISE, don't report its location */ + if (estate->err_text == raise_skip_msg) + return; + + if (estate->err_stmt != NULL) + errcontext("PL/pgSQL function %s line %d at %s", + estate->err_func->fn_name, + estate->err_stmt->lineno, + plpgsql_stmt_typename(estate->err_stmt)); + else if (estate->err_text != NULL) + errcontext("PL/pgSQL function %s %s", + estate->err_func->fn_name, + estate->err_text); + else + errcontext("PL/pgSQL function %s", + estate->err_func->fn_name); +} + + /* ---------- * Support functions for copying local execution variables * ---------- @@ -909,8 +862,8 @@ exec_stmt(PLpgSQL_execstate * estate, PLpgSQL_stmt * stmt) PLpgSQL_stmt *save_estmt; int rc = -1; - save_estmt = error_info_stmt; - error_info_stmt = stmt; + save_estmt = estate->err_stmt; + estate->err_stmt = stmt; CHECK_FOR_INTERRUPTS(); @@ -997,12 +950,12 @@ exec_stmt(PLpgSQL_execstate * estate, PLpgSQL_stmt * stmt) break; default: - error_info_stmt = save_estmt; + estate->err_stmt = save_estmt; elog(ERROR, "unknown cmdtype %d in exec_stmt", stmt->cmd_type); } - error_info_stmt = save_estmt; + estate->err_stmt = save_estmt; return rc; } @@ -1854,15 +1807,15 @@ exec_stmt_raise(PLpgSQL_execstate * estate, PLpgSQL_stmt_raise * stmt) } /* - * Now suppress debug info and throw the elog() + * Throw the error (may or may not come back) */ - if (stmt->elog_level == ERROR) - { - error_info_func = NULL; - error_info_stmt = NULL; - error_info_text = NULL; - } - elog(stmt->elog_level, "%s", plpgsql_dstring_get(&ds)); + estate->err_text = raise_skip_msg; /* suppress traceback of raise */ + + ereport(stmt->elog_level, + (errmsg_internal("%s", plpgsql_dstring_get(&ds)))); + + estate->err_text = NULL; /* un-suppress... */ + plpgsql_dstring_free(&ds); return PLPGSQL_RC_OK; @@ -1905,6 +1858,10 @@ plpgsql_estate_setup(PLpgSQL_execstate * estate, estate->eval_processed = 0; estate->eval_lastoid = InvalidOid; estate->eval_econtext = NULL; + + estate->err_func = func; + estate->err_stmt = NULL; + estate->err_text = NULL; } /* ---------- diff --git a/src/pl/plpgsql/src/plpgsql.h b/src/pl/plpgsql/src/plpgsql.h index 9706db602c..3756d94c91 100644 --- a/src/pl/plpgsql/src/plpgsql.h +++ b/src/pl/plpgsql/src/plpgsql.h @@ -3,7 +3,7 @@ * procedural language * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/pl/plpgsql/src/plpgsql.h,v 1.33 2003/03/25 03:16:41 tgl Exp $ + * $Header: /cvsroot/pgsql/src/pl/plpgsql/src/plpgsql.h,v 1.34 2003/04/24 21:16:44 tgl Exp $ * * This software is copyrighted by Jan Wieck - Hamburg. * @@ -552,6 +552,11 @@ typedef struct uint32 eval_processed; Oid eval_lastoid; ExprContext *eval_econtext; + + /* status information for error context reporting */ + PLpgSQL_function *err_func; /* current func */ + PLpgSQL_stmt *err_stmt; /* current stmt */ + const char *err_text; /* additional state info */ } PLpgSQL_execstate; diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out index 4d1458f891..26c8c2ed42 100644 --- a/src/test/regress/expected/alter_table.out +++ b/src/test/regress/expected/alter_table.out @@ -994,7 +994,8 @@ ERROR: Relation "test" has no column "a" copy test("........pg.dropped.1........") to stdout; ERROR: Relation "test" has no column "........pg.dropped.1........" copy test from stdin; -ERROR: copy: line 1, Extra data after last expected column +ERROR: Extra data after last expected column +CONTEXT: COPY FROM, line 1 SET autocommit TO 'on'; select * from test; b | c diff --git a/src/test/regress/expected/copy2.out b/src/test/regress/expected/copy2.out index 983e6bb4a4..2d69434b5b 100644 --- a/src/test/regress/expected/copy2.out +++ b/src/test/regress/expected/copy2.out @@ -34,14 +34,18 @@ COPY x (a, b, c, d, e, d, c) from stdin; ERROR: Attribute "d" specified more than once -- missing data: should fail COPY x from stdin; -ERROR: copy: line 1, pg_atoi: zero-length string +ERROR: pg_atoi: zero-length string +CONTEXT: COPY FROM, line 1 COPY x from stdin; -ERROR: copy: line 1, Missing data for column "e" +ERROR: Missing data for column "e" +CONTEXT: COPY FROM, line 1 COPY x from stdin; -ERROR: copy: line 1, Missing data for column "e" +ERROR: Missing data for column "e" +CONTEXT: COPY FROM, line 1 -- extra data: should fail COPY x from stdin; -ERROR: copy: line 1, Extra data after last expected column +ERROR: Extra data after last expected column +CONTEXT: COPY FROM, line 1 SET autocommit TO 'on'; -- various COPY options: delimiters, oids, NULL string COPY x (b, c, d, e) from stdin with oids delimiter ',' null 'x'; diff --git a/src/test/regress/expected/domain.out b/src/test/regress/expected/domain.out index 13eb14cfa2..299bae55f8 100644 --- a/src/test/regress/expected/domain.out +++ b/src/test/regress/expected/domain.out @@ -39,7 +39,8 @@ ERROR: value too long for type character varying(5) INSERT INTO basictest values ('88', 'haha', 'short', '123.1212'); -- Truncate numeric -- Test copy COPY basictest (testvarchar) FROM stdin; -- fail -ERROR: copy: line 1, value too long for type character varying(5) +ERROR: value too long for type character varying(5) +CONTEXT: COPY FROM, line 1 SET autocommit TO 'on'; COPY basictest (testvarchar) FROM stdin; select * from basictest; @@ -126,11 +127,13 @@ ERROR: ExecInsert: Fail to add null value in not null attribute col3 INSERT INTO nulltest values ('a', 'b', 'c', NULL, 'd'); -- Good -- Test copy COPY nulltest FROM stdin; --fail -ERROR: copy: line 1, Domain dcheck does not allow NULL values +ERROR: Domain dcheck does not allow NULL values +CONTEXT: COPY FROM, line 1 SET autocommit TO 'on'; -- Last row is bad COPY nulltest FROM stdin; -ERROR: copy: line 3, CopyFrom: rejected due to CHECK constraint "nulltest_col5" on "nulltest" +ERROR: CopyFrom: rejected due to CHECK constraint "nulltest_col5" on "nulltest" +CONTEXT: COPY FROM, line 3 select * from nulltest; col1 | col2 | col3 | col4 | col5 ------+------+------+------+------ diff --git a/src/test/regress/expected/plpgsql.out b/src/test/regress/expected/plpgsql.out index b300b695d9..6ef512a218 100644 --- a/src/test/regress/expected/plpgsql.out +++ b/src/test/regress/expected/plpgsql.out @@ -1518,12 +1518,16 @@ insert into PField values ('PF1_1', 'should fail due to unique index'); ERROR: Cannot insert a duplicate key into unique index pfield_name update PSlot set backlink = 'WS.not.there' where slotname = 'PS.base.a1'; ERROR: WS.not.there does not exist +CONTEXT: PL/pgSQL function tg_backlink_a line 16 at assignment update PSlot set backlink = 'XX.illegal' where slotname = 'PS.base.a1'; ERROR: illegal backlink beginning with XX +CONTEXT: PL/pgSQL function tg_backlink_a line 16 at assignment update PSlot set slotlink = 'PS.not.there' where slotname = 'PS.base.a1'; ERROR: PS.not.there does not exist +CONTEXT: PL/pgSQL function tg_slotlink_a line 16 at assignment update PSlot set slotlink = 'XX.illegal' where slotname = 'PS.base.a1'; ERROR: illegal slotlink beginning with XX +CONTEXT: PL/pgSQL function tg_slotlink_a line 16 at assignment insert into HSlot values ('HS', 'base.hub1', 1, ''); ERROR: Cannot insert a duplicate key into unique index hslot_name insert into HSlot values ('HS', 'base.hub1', 20, ''); diff --git a/src/test/regress/output/constraints.source b/src/test/regress/output/constraints.source index 7df5e414e8..7a0e335509 100644 --- a/src/test/regress/output/constraints.source +++ b/src/test/regress/output/constraints.source @@ -273,7 +273,8 @@ SELECT '' AS two, * FROM COPY_TBL; (2 rows) COPY COPY_TBL FROM '@abs_srcdir@/data/constrf.data'; -ERROR: copy: line 2, CopyFrom: rejected due to CHECK constraint "copy_con" on "copy_tbl" +ERROR: CopyFrom: rejected due to CHECK constraint "copy_con" on "copy_tbl" +CONTEXT: COPY FROM, line 2 SELECT * FROM COPY_TBL; x | y | z ---+---------------+---