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 ---+---------------+---