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:
@@ -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.
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user