diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index 2328d8f5f2..3829a1400d 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -2691,6 +2691,48 @@ char *PQresultErrorMessage(const PGresult *res);
+
+
+ PQresultVerboseErrorMessage
+
+ PQresultVerboseErrorMessage
+
+
+
+
+
+ Returns a reformatted version of the error message associated with
+ a PGresult> object.
+
+char *PQresultVerboseErrorMessage(const PGresult *res,
+ PGVerbosity verbosity,
+ PGContextVisibility show_context);
+
+ In some situations a client might wish to obtain a more detailed
+ version of a previously-reported error.
+ PQresultVerboseErrorMessage addresses this need
+ by computing the message that would have been produced
+ by PQresultErrorMessage if the specified
+ verbosity settings had been in effect for the connection when the
+ given PGresult> was generated. If
+ the PGresult> is not an error result,
+ PGresult is not an error result> is reported instead.
+ The returned string includes a trailing newline.
+
+
+
+ Unlike most other functions for extracting data from
+ a PGresult>, the result of this function is a freshly
+ allocated string. The caller must free it
+ using PQfreemem()> when the string is no longer needed.
+
+
+
+ A NULL return is possible if there is insufficient memory.
+
+
+
+
PQresultErrorFieldPQresultErrorField>>
@@ -5582,6 +5624,8 @@ PGVerbosity PQsetErrorVerbosity(PGconn *conn, PGVerbosity verbosity);
mode includes all available fields. Changing the verbosity does not
affect the messages available from already-existing
PGresult> objects, only subsequently-created ones.
+ (But see PQresultVerboseErrorMessage if you
+ want to print a previous error with a different verbosity.)
@@ -5622,6 +5666,8 @@ PGContextVisibility PQsetErrorContextVisibility(PGconn *conn, PGContextVisibilit
affect the messages available from
already-existing PGresult> objects, only
subsequently-created ones.
+ (But see PQresultVerboseErrorMessage if you
+ want to print a previous error with a different display mode.)
@@ -6089,8 +6135,9 @@ PQsetNoticeProcessor(PGconn *conn,
receiver function is called. It is passed the message in the form of
a PGRES_NONFATAL_ERROR
PGresult. (This allows the receiver to extract
- individual fields using PQresultErrorField>, or the complete
- preformatted message using PQresultErrorMessage>.) The same
+ individual fields using PQresultErrorField>, or obtain a
+ complete preformatted message using PQresultErrorMessage>
+ or PQresultVerboseErrorMessage>.) The same
void pointer passed to PQsetNoticeReceiver is also
passed. (This pointer can be used to access application-specific state
if needed.)
diff --git a/src/interfaces/libpq/exports.txt b/src/interfaces/libpq/exports.txt
index c69a4d5ea4..21dd772ca9 100644
--- a/src/interfaces/libpq/exports.txt
+++ b/src/interfaces/libpq/exports.txt
@@ -170,3 +170,4 @@ PQsslStruct 167
PQsslAttributeNames 168
PQsslAttribute 169
PQsetErrorContextVisibility 170
+PQresultVerboseErrorMessage 171
diff --git a/src/interfaces/libpq/fe-exec.c b/src/interfaces/libpq/fe-exec.c
index 41937c0bf9..2621767fd4 100644
--- a/src/interfaces/libpq/fe-exec.c
+++ b/src/interfaces/libpq/fe-exec.c
@@ -159,6 +159,7 @@ PQmakeEmptyPGresult(PGconn *conn, ExecStatusType status)
result->nEvents = 0;
result->errMsg = NULL;
result->errFields = NULL;
+ result->errQuery = NULL;
result->null_field[0] = '\0';
result->curBlock = NULL;
result->curOffset = 0;
@@ -2598,6 +2599,44 @@ PQresultErrorMessage(const PGresult *res)
return res->errMsg;
}
+char *
+PQresultVerboseErrorMessage(const PGresult *res,
+ PGVerbosity verbosity,
+ PGContextVisibility show_context)
+{
+ PQExpBufferData workBuf;
+
+ /*
+ * Because the caller is expected to free the result string, we must
+ * strdup any constant result. We use plain strdup and document that
+ * callers should expect NULL if out-of-memory.
+ */
+ if (!res ||
+ (res->resultStatus != PGRES_FATAL_ERROR &&
+ res->resultStatus != PGRES_NONFATAL_ERROR))
+ return strdup(libpq_gettext("PGresult is not an error result\n"));
+
+ initPQExpBuffer(&workBuf);
+
+ /*
+ * Currently, we pass this off to fe-protocol3.c in all cases; it will
+ * behave reasonably sanely with an error reported by fe-protocol2.c as
+ * well. If necessary, we could record the protocol version in PGresults
+ * so as to be able to invoke a version-specific message formatter, but
+ * for now there's no need.
+ */
+ pqBuildErrorMessage3(&workBuf, res, verbosity, show_context);
+
+ /* If insufficient memory to format the message, fail cleanly */
+ if (PQExpBufferDataBroken(workBuf))
+ {
+ termPQExpBuffer(&workBuf);
+ return strdup(libpq_gettext("out of memory\n"));
+ }
+
+ return workBuf.data;
+}
+
char *
PQresultErrorField(const PGresult *res, int fieldcode)
{
diff --git a/src/interfaces/libpq/fe-protocol3.c b/src/interfaces/libpq/fe-protocol3.c
index 3034773972..0b8c62f6ce 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,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.
*
diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h
index 6bf34b3e99..9ca0756c4b 100644
--- a/src/interfaces/libpq/libpq-fe.h
+++ b/src/interfaces/libpq/libpq-fe.h
@@ -463,6 +463,9 @@ extern PGresult *PQfn(PGconn *conn,
extern ExecStatusType PQresultStatus(const PGresult *res);
extern char *PQresStatus(ExecStatusType status);
extern char *PQresultErrorMessage(const PGresult *res);
+extern char *PQresultVerboseErrorMessage(const PGresult *res,
+ PGVerbosity verbosity,
+ PGContextVisibility show_context);
extern char *PQresultErrorField(const PGresult *res, int fieldcode);
extern int PQntuples(const PGresult *res);
extern int PQnfields(const PGresult *res);
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index 6c9bbf7760..1183323a44 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -197,6 +197,7 @@ struct pg_result
*/
char *errMsg; /* error message, or NULL if no error */
PGMessageField *errFields; /* message broken into fields */
+ char *errQuery; /* text of triggering query, if available */
/* All NULL attributes in the query result point to this null string */
char null_field[1];
@@ -575,6 +576,8 @@ extern char *pqBuildStartupPacket3(PGconn *conn, int *packetlen,
const PQEnvironmentOption *options);
extern void pqParseInput3(PGconn *conn);
extern int pqGetErrorNotice3(PGconn *conn, bool isError);
+extern void pqBuildErrorMessage3(PQExpBuffer msg, const PGresult *res,
+ PGVerbosity verbosity, PGContextVisibility show_context);
extern int pqGetCopyData3(PGconn *conn, char **buffer, int async);
extern int pqGetline3(PGconn *conn, char *s, int maxlen);
extern int pqGetlineAsync3(PGconn *conn, char *buffer, int bufsize);