aboutsummaryrefslogtreecommitdiff
path: root/src/interfaces/libpq/fe-protocol3.c
diff options
context:
space:
mode:
authorTom Lane <tgl@sss.pgh.pa.us>2016-04-03 12:24:54 -0400
committerTom Lane <tgl@sss.pgh.pa.us>2016-04-03 12:24:54 -0400
commite3161b231cfaadd4b6438eff2fc1f6cd086f41a9 (patch)
treeffdeda3132d853aa681bb871230f81d4f5badf77 /src/interfaces/libpq/fe-protocol3.c
parent5a5b917184b630529635db2e037d298ad90c355d (diff)
downloadpostgresql-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.c197
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;
}
/*