1
0
mirror of https://github.com/postgres/postgres.git synced 2025-11-19 13:42:17 +03:00

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
This commit is contained in:
Tom Lane
2016-04-03 12:24:54 -04:00
parent 5a5b917184
commit e3161b231c
6 changed files with 264 additions and 124 deletions

View File

@@ -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,131 +916,26 @@ 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);
val = PQresultErrorField(res, PG_DIAG_SEVERITY);
if (val)
appendPQExpBuffer(&workBuf, "%s: ", val);
val = PQresultErrorField(res, PG_DIAG_SQLSTATE);
if (val)
{
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_MESSAGE_PRIMARY);
if (val)
appendPQExpBufferStr(&workBuf, val);
val = PQresultErrorField(res, PG_DIAG_STATEMENT_POSITION);
if (val)
{
if (conn->verbosity != PQERRORS_TERSE && conn->last_query != NULL)
{
/* emit position as a syntax cursor display */
querytext = conn->last_query;
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"),
val);
}
}
else
{
val = PQresultErrorField(res, PG_DIAG_INTERNAL_POSITION);
if (val)
{
querytext = PQresultErrorField(res, PG_DIAG_INTERNAL_QUERY);
if (conn->verbosity != PQERRORS_TERSE && querytext != NULL)
{
/* emit position as a syntax cursor display */
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"),
val);
}
}
}
appendPQExpBufferChar(&workBuf, '\n');
if (conn->verbosity != PQERRORS_TERSE)
{
if (querytext && querypos > 0)
reportErrorPosition(&workBuf, querytext, querypos,
conn->client_encoding);
val = PQresultErrorField(res, PG_DIAG_MESSAGE_DETAIL);
if (val)
appendPQExpBuffer(&workBuf, libpq_gettext("DETAIL: %s\n"), val);
val = PQresultErrorField(res, PG_DIAG_MESSAGE_HINT);
if (val)
appendPQExpBuffer(&workBuf, 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))
{
val = PQresultErrorField(res, PG_DIAG_CONTEXT);
if (val)
appendPQExpBuffer(&workBuf, libpq_gettext("CONTEXT: %s\n"),
val);
}
}
if (conn->verbosity == PQERRORS_VERBOSE)
{
val = PQresultErrorField(res, PG_DIAG_SCHEMA_NAME);
if (val)
appendPQExpBuffer(&workBuf,
libpq_gettext("SCHEMA NAME: %s\n"), val);
val = PQresultErrorField(res, PG_DIAG_TABLE_NAME);
if (val)
appendPQExpBuffer(&workBuf,
libpq_gettext("TABLE NAME: %s\n"), val);
val = PQresultErrorField(res, PG_DIAG_COLUMN_NAME);
if (val)
appendPQExpBuffer(&workBuf,
libpq_gettext("COLUMN NAME: %s\n"), val);
val = PQresultErrorField(res, PG_DIAG_DATATYPE_NAME);
if (val)
appendPQExpBuffer(&workBuf,
libpq_gettext("DATATYPE NAME: %s\n"), val);
val = PQresultErrorField(res, PG_DIAG_CONSTRAINT_NAME);
if (val)
appendPQExpBuffer(&workBuf,
libpq_gettext("CONSTRAINT NAME: %s\n"), val);
}
if (conn->verbosity == PQERRORS_VERBOSE)
{
const char *valf;
const char *vall;
valf = PQresultErrorField(res, PG_DIAG_SOURCE_FILE);
vall = PQresultErrorField(res, PG_DIAG_SOURCE_LINE);
val = PQresultErrorField(res, PG_DIAG_SOURCE_FUNCTION);
if (val || valf || vall)
{
appendPQExpBufferStr(&workBuf, libpq_gettext("LOCATION: "));
if (val)
appendPQExpBuffer(&workBuf, libpq_gettext("%s, "), val);
if (valf && vall) /* unlikely we'd have just one */
appendPQExpBuffer(&workBuf, libpq_gettext("%s:%s"),
valf, vall);
appendPQExpBufferChar(&workBuf, '\n');
}
}
pqBuildErrorMessage3(&workBuf, res, conn->verbosity, conn->show_context);
/*
* Either save error as current async result, or just emit the notice.
@@ -1078,6 +974,157 @@ fail:
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(msg, "%s: ", val);
if (verbosity == PQERRORS_VERBOSE)
{
val = PQresultErrorField(res, PG_DIAG_SQLSTATE);
if (val)
appendPQExpBuffer(msg, "%s: ", val);
}
val = PQresultErrorField(res, PG_DIAG_MESSAGE_PRIMARY);
if (val)
appendPQExpBufferStr(msg, val);
val = PQresultErrorField(res, PG_DIAG_STATEMENT_POSITION);
if (val)
{
if (verbosity != PQERRORS_TERSE && res->errQuery != NULL)
{
/* emit position as a syntax cursor display */
querytext = res->errQuery;
querypos = atoi(val);
}
else
{
/* emit position as text addition to primary message */
/* translator: %s represents a digit string */
appendPQExpBuffer(msg, libpq_gettext(" at character %s"),
val);
}
}
else
{
val = PQresultErrorField(res, PG_DIAG_INTERNAL_POSITION);
if (val)
{
querytext = PQresultErrorField(res, PG_DIAG_INTERNAL_QUERY);
if (verbosity != PQERRORS_TERSE && querytext != NULL)
{
/* emit position as a syntax cursor display */
querypos = atoi(val);
}
else
{
/* emit position as text addition to primary message */
/* translator: %s represents a digit string */
appendPQExpBuffer(msg, libpq_gettext(" at character %s"),
val);
}
}
}
appendPQExpBufferChar(msg, '\n');
if (verbosity != PQERRORS_TERSE)
{
if (querytext && querypos > 0)
reportErrorPosition(msg, querytext, querypos,
res->client_encoding);
val = PQresultErrorField(res, PG_DIAG_MESSAGE_DETAIL);
if (val)
appendPQExpBuffer(msg, libpq_gettext("DETAIL: %s\n"), val);
val = PQresultErrorField(res, PG_DIAG_MESSAGE_HINT);
if (val)
appendPQExpBuffer(msg, libpq_gettext("HINT: %s\n"), val);
val = PQresultErrorField(res, PG_DIAG_INTERNAL_QUERY);
if (val)
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(msg, libpq_gettext("CONTEXT: %s\n"),
val);
}
}
if (verbosity == PQERRORS_VERBOSE)
{
val = PQresultErrorField(res, PG_DIAG_SCHEMA_NAME);
if (val)
appendPQExpBuffer(msg,
libpq_gettext("SCHEMA NAME: %s\n"), val);
val = PQresultErrorField(res, PG_DIAG_TABLE_NAME);
if (val)
appendPQExpBuffer(msg,
libpq_gettext("TABLE NAME: %s\n"), val);
val = PQresultErrorField(res, PG_DIAG_COLUMN_NAME);
if (val)
appendPQExpBuffer(msg,
libpq_gettext("COLUMN NAME: %s\n"), val);
val = PQresultErrorField(res, PG_DIAG_DATATYPE_NAME);
if (val)
appendPQExpBuffer(msg,
libpq_gettext("DATATYPE NAME: %s\n"), val);
val = PQresultErrorField(res, PG_DIAG_CONSTRAINT_NAME);
if (val)
appendPQExpBuffer(msg,
libpq_gettext("CONSTRAINT NAME: %s\n"), val);
}
if (verbosity == PQERRORS_VERBOSE)
{
const char *valf;
const char *vall;
valf = PQresultErrorField(res, PG_DIAG_SOURCE_FILE);
vall = PQresultErrorField(res, PG_DIAG_SOURCE_LINE);
val = PQresultErrorField(res, PG_DIAG_SOURCE_FUNCTION);
if (val || valf || vall)
{
appendPQExpBufferStr(msg, libpq_gettext("LOCATION: "));
if (val)
appendPQExpBuffer(msg, libpq_gettext("%s, "), val);
if (valf && vall) /* unlikely we'd have just one */
appendPQExpBuffer(msg, libpq_gettext("%s:%s"),
valf, vall);
appendPQExpBufferChar(msg, '\n');
}
}
}
/*
* Add an error-location display to the error message under construction.
*