diff options
author | Tom Lane <tgl@sss.pgh.pa.us> | 2016-04-03 12:24:54 -0400 |
---|---|---|
committer | Tom Lane <tgl@sss.pgh.pa.us> | 2016-04-03 12:24:54 -0400 |
commit | e3161b231cfaadd4b6438eff2fc1f6cd086f41a9 (patch) | |
tree | ffdeda3132d853aa681bb871230f81d4f5badf77 /src/interfaces/libpq/fe-protocol3.c | |
parent | 5a5b917184b630529635db2e037d298ad90c355d (diff) | |
download | postgresql-e3161b231cfaadd4b6438eff2fc1f6cd086f41a9.tar.gz postgresql-e3161b231cfaadd4b6438eff2fc1f6cd086f41a9.zip |
Add libpq support for recreating an error message with different verbosity.
Often, upon getting an unexpected error in psql, one's first wish is that
the verbosity setting had been higher; for example, to be able to see the
schema-name field or the server code location info. Up to now the only way
has been to adjust the VERBOSITY variable and repeat the failing query.
That's a pain, and it doesn't work if the error isn't reproducible.
This commit adds support in libpq for regenerating the error message for
an existing error PGresult at any desired verbosity level. This is almost
just a matter of refactoring the existing code into a subroutine, but there
is one bit of possibly-needed information that was not getting put into
PGresults: the text of the last query sent to the server. We must add that
string to the contents of an error PGresult. But we only need to save it
if it might be used, which with the existing error-formatting code only
happens if there is a PG_DIAG_STATEMENT_POSITION error field, which is
probably pretty rare for errors in production situations. So really the
overhead when the feature isn't used should be negligible.
Alex Shulgin, reviewed by Daniel Vérité, some improvements by me
Diffstat (limited to 'src/interfaces/libpq/fe-protocol3.c')
-rw-r--r-- | src/interfaces/libpq/fe-protocol3.c | 197 |
1 files changed, 122 insertions, 75 deletions
diff --git a/src/interfaces/libpq/fe-protocol3.c b/src/interfaces/libpq/fe-protocol3.c index 3034773972a..0b8c62f6ce2 100644 --- a/src/interfaces/libpq/fe-protocol3.c +++ b/src/interfaces/libpq/fe-protocol3.c @@ -876,11 +876,9 @@ int pqGetErrorNotice3(PGconn *conn, bool isError) { PGresult *res = NULL; + bool have_position = false; PQExpBufferData workBuf; char id; - const char *val; - const char *querytext = NULL; - int querypos = 0; /* * Since the fields might be pretty long, we create a temporary @@ -905,6 +903,9 @@ pqGetErrorNotice3(PGconn *conn, bool isError) /* * Read the fields and save into res. + * + * While at it, save the SQLSTATE in conn->last_sqlstate, and note whether + * we saw a PG_DIAG_STATEMENT_POSITION field. */ for (;;) { @@ -915,42 +916,123 @@ pqGetErrorNotice3(PGconn *conn, bool isError) if (pqGets(&workBuf, conn)) goto fail; pqSaveMessageField(res, id, workBuf.data); + if (id == PG_DIAG_SQLSTATE) + strlcpy(conn->last_sqlstate, workBuf.data, + sizeof(conn->last_sqlstate)); + else if (id == PG_DIAG_STATEMENT_POSITION) + have_position = true; } /* + * Save the active query text, if any, into res as well; but only if we + * might need it for an error cursor display, which is only true if there + * is a PG_DIAG_STATEMENT_POSITION field. + */ + if (have_position && conn->last_query && res) + res->errQuery = pqResultStrdup(res, conn->last_query); + + /* * Now build the "overall" error message for PQresultErrorMessage. - * - * Also, save the SQLSTATE in conn->last_sqlstate. */ resetPQExpBuffer(&workBuf); + pqBuildErrorMessage3(&workBuf, res, conn->verbosity, conn->show_context); + + /* + * Either save error as current async result, or just emit the notice. + */ + if (isError) + { + if (res) + res->errMsg = pqResultStrdup(res, workBuf.data); + pqClearAsyncResult(conn); + conn->result = res; + if (PQExpBufferDataBroken(workBuf)) + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("out of memory")); + else + appendPQExpBufferStr(&conn->errorMessage, workBuf.data); + } + else + { + /* if we couldn't allocate the result set, just discard the NOTICE */ + if (res) + { + /* We can cheat a little here and not copy the message. */ + res->errMsg = workBuf.data; + if (res->noticeHooks.noticeRec != NULL) + (*res->noticeHooks.noticeRec) (res->noticeHooks.noticeRecArg, res); + PQclear(res); + } + } + + termPQExpBuffer(&workBuf); + return 0; + +fail: + PQclear(res); + termPQExpBuffer(&workBuf); + return EOF; +} + +/* + * Construct an error message from the fields in the given PGresult, + * appending it to the contents of "msg". + */ +void +pqBuildErrorMessage3(PQExpBuffer msg, const PGresult *res, + PGVerbosity verbosity, PGContextVisibility show_context) +{ + const char *val; + const char *querytext = NULL; + int querypos = 0; + + /* If we couldn't allocate a PGresult, just say "out of memory" */ + if (res == NULL) + { + appendPQExpBuffer(msg, libpq_gettext("out of memory\n")); + return; + } + + /* + * If we don't have any broken-down fields, just return the base message. + * This mainly applies if we're given a libpq-generated error result. + */ + if (res->errFields == NULL) + { + if (res->errMsg && res->errMsg[0]) + appendPQExpBufferStr(msg, res->errMsg); + else + appendPQExpBuffer(msg, libpq_gettext("no error message available\n")); + return; + } + + /* Else build error message from relevant fields */ val = PQresultErrorField(res, PG_DIAG_SEVERITY); if (val) - appendPQExpBuffer(&workBuf, "%s: ", val); - val = PQresultErrorField(res, PG_DIAG_SQLSTATE); - if (val) + appendPQExpBuffer(msg, "%s: ", val); + if (verbosity == PQERRORS_VERBOSE) { - if (strlen(val) < sizeof(conn->last_sqlstate)) - strcpy(conn->last_sqlstate, val); - if (conn->verbosity == PQERRORS_VERBOSE) - appendPQExpBuffer(&workBuf, "%s: ", val); + val = PQresultErrorField(res, PG_DIAG_SQLSTATE); + if (val) + appendPQExpBuffer(msg, "%s: ", val); } val = PQresultErrorField(res, PG_DIAG_MESSAGE_PRIMARY); if (val) - appendPQExpBufferStr(&workBuf, val); + appendPQExpBufferStr(msg, val); val = PQresultErrorField(res, PG_DIAG_STATEMENT_POSITION); if (val) { - if (conn->verbosity != PQERRORS_TERSE && conn->last_query != NULL) + if (verbosity != PQERRORS_TERSE && res->errQuery != NULL) { /* emit position as a syntax cursor display */ - querytext = conn->last_query; + querytext = res->errQuery; querypos = atoi(val); } else { /* emit position as text addition to primary message */ /* translator: %s represents a digit string */ - appendPQExpBuffer(&workBuf, libpq_gettext(" at character %s"), + appendPQExpBuffer(msg, libpq_gettext(" at character %s"), val); } } @@ -960,7 +1042,7 @@ pqGetErrorNotice3(PGconn *conn, bool isError) if (val) { querytext = PQresultErrorField(res, PG_DIAG_INTERNAL_QUERY); - if (conn->verbosity != PQERRORS_TERSE && querytext != NULL) + if (verbosity != PQERRORS_TERSE && querytext != NULL) { /* emit position as a syntax cursor display */ querypos = atoi(val); @@ -969,59 +1051,60 @@ pqGetErrorNotice3(PGconn *conn, bool isError) { /* emit position as text addition to primary message */ /* translator: %s represents a digit string */ - appendPQExpBuffer(&workBuf, libpq_gettext(" at character %s"), + appendPQExpBuffer(msg, libpq_gettext(" at character %s"), val); } } } - appendPQExpBufferChar(&workBuf, '\n'); - if (conn->verbosity != PQERRORS_TERSE) + appendPQExpBufferChar(msg, '\n'); + if (verbosity != PQERRORS_TERSE) { if (querytext && querypos > 0) - reportErrorPosition(&workBuf, querytext, querypos, - conn->client_encoding); + reportErrorPosition(msg, querytext, querypos, + res->client_encoding); val = PQresultErrorField(res, PG_DIAG_MESSAGE_DETAIL); if (val) - appendPQExpBuffer(&workBuf, libpq_gettext("DETAIL: %s\n"), val); + appendPQExpBuffer(msg, libpq_gettext("DETAIL: %s\n"), val); val = PQresultErrorField(res, PG_DIAG_MESSAGE_HINT); if (val) - appendPQExpBuffer(&workBuf, libpq_gettext("HINT: %s\n"), val); + appendPQExpBuffer(msg, libpq_gettext("HINT: %s\n"), val); val = PQresultErrorField(res, PG_DIAG_INTERNAL_QUERY); if (val) - appendPQExpBuffer(&workBuf, libpq_gettext("QUERY: %s\n"), val); - if (conn->show_context == PQSHOW_CONTEXT_ALWAYS || - (conn->show_context == PQSHOW_CONTEXT_ERRORS && isError)) + appendPQExpBuffer(msg, libpq_gettext("QUERY: %s\n"), val); + if (show_context == PQSHOW_CONTEXT_ALWAYS || + (show_context == PQSHOW_CONTEXT_ERRORS && + res->resultStatus == PGRES_FATAL_ERROR)) { val = PQresultErrorField(res, PG_DIAG_CONTEXT); if (val) - appendPQExpBuffer(&workBuf, libpq_gettext("CONTEXT: %s\n"), + appendPQExpBuffer(msg, libpq_gettext("CONTEXT: %s\n"), val); } } - if (conn->verbosity == PQERRORS_VERBOSE) + if (verbosity == PQERRORS_VERBOSE) { val = PQresultErrorField(res, PG_DIAG_SCHEMA_NAME); if (val) - appendPQExpBuffer(&workBuf, + appendPQExpBuffer(msg, libpq_gettext("SCHEMA NAME: %s\n"), val); val = PQresultErrorField(res, PG_DIAG_TABLE_NAME); if (val) - appendPQExpBuffer(&workBuf, + appendPQExpBuffer(msg, libpq_gettext("TABLE NAME: %s\n"), val); val = PQresultErrorField(res, PG_DIAG_COLUMN_NAME); if (val) - appendPQExpBuffer(&workBuf, + appendPQExpBuffer(msg, libpq_gettext("COLUMN NAME: %s\n"), val); val = PQresultErrorField(res, PG_DIAG_DATATYPE_NAME); if (val) - appendPQExpBuffer(&workBuf, + appendPQExpBuffer(msg, libpq_gettext("DATATYPE NAME: %s\n"), val); val = PQresultErrorField(res, PG_DIAG_CONSTRAINT_NAME); if (val) - appendPQExpBuffer(&workBuf, + appendPQExpBuffer(msg, libpq_gettext("CONSTRAINT NAME: %s\n"), val); } - if (conn->verbosity == PQERRORS_VERBOSE) + if (verbosity == PQERRORS_VERBOSE) { const char *valf; const char *vall; @@ -1031,51 +1114,15 @@ pqGetErrorNotice3(PGconn *conn, bool isError) val = PQresultErrorField(res, PG_DIAG_SOURCE_FUNCTION); if (val || valf || vall) { - appendPQExpBufferStr(&workBuf, libpq_gettext("LOCATION: ")); + appendPQExpBufferStr(msg, libpq_gettext("LOCATION: ")); if (val) - appendPQExpBuffer(&workBuf, libpq_gettext("%s, "), val); + appendPQExpBuffer(msg, libpq_gettext("%s, "), val); if (valf && vall) /* unlikely we'd have just one */ - appendPQExpBuffer(&workBuf, libpq_gettext("%s:%s"), + appendPQExpBuffer(msg, libpq_gettext("%s:%s"), valf, vall); - appendPQExpBufferChar(&workBuf, '\n'); + appendPQExpBufferChar(msg, '\n'); } } - - /* - * Either save error as current async result, or just emit the notice. - */ - if (isError) - { - if (res) - res->errMsg = pqResultStrdup(res, workBuf.data); - pqClearAsyncResult(conn); - conn->result = res; - if (PQExpBufferDataBroken(workBuf)) - printfPQExpBuffer(&conn->errorMessage, - libpq_gettext("out of memory")); - else - appendPQExpBufferStr(&conn->errorMessage, workBuf.data); - } - else - { - /* if we couldn't allocate the result set, just discard the NOTICE */ - if (res) - { - /* We can cheat a little here and not copy the message. */ - res->errMsg = workBuf.data; - if (res->noticeHooks.noticeRec != NULL) - (*res->noticeHooks.noticeRec) (res->noticeHooks.noticeRecArg, res); - PQclear(res); - } - } - - termPQExpBuffer(&workBuf); - return 0; - -fail: - PQclear(res); - termPQExpBuffer(&workBuf); - return EOF; } /* |