diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml index 2328d8f5f21224a68b4dc4bf823159b8348920d2..3829a1400d93720812e9eb55adadda7326d4f106 100644 --- a/doc/src/sgml/libpq.sgml +++ b/doc/src/sgml/libpq.sgml @@ -2691,6 +2691,48 @@ char *PQresultErrorMessage(const PGresult *res); </listitem> </varlistentry> + <varlistentry id="libpq-pqresultverboseerrormessage"> + <term> + <function>PQresultVerboseErrorMessage</function> + <indexterm> + <primary>PQresultVerboseErrorMessage</primary> + </indexterm> + </term> + + <listitem> + <para> + Returns a reformatted version of the error message associated with + a <structname>PGresult</> object. +<synopsis> +char *PQresultVerboseErrorMessage(const PGresult *res, + PGVerbosity verbosity, + PGContextVisibility show_context); +</synopsis> + In some situations a client might wish to obtain a more detailed + version of a previously-reported error. + <function>PQresultVerboseErrorMessage</function> addresses this need + by computing the message that would have been produced + by <function>PQresultErrorMessage</function> if the specified + verbosity settings had been in effect for the connection when the + given <structname>PGresult</> was generated. If + the <structname>PGresult</> is not an error result, + <quote>PGresult is not an error result</> is reported instead. + The returned string includes a trailing newline. + </para> + + <para> + Unlike most other functions for extracting data from + a <structname>PGresult</>, the result of this function is a freshly + allocated string. The caller must free it + using <function>PQfreemem()</> when the string is no longer needed. + </para> + + <para> + A NULL return is possible if there is insufficient memory. + </para> + </listitem> + </varlistentry> + <varlistentry id="libpq-pqresulterrorfield"> <term><function>PQresultErrorField</function><indexterm><primary>PQresultErrorField</></></term> <listitem> @@ -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 <structname>PGresult</> objects, only subsequently-created ones. + (But see <function>PQresultVerboseErrorMessage</function> if you + want to print a previous error with a different verbosity.) </para> </listitem> </varlistentry> @@ -5622,6 +5666,8 @@ PGContextVisibility PQsetErrorContextVisibility(PGconn *conn, PGContextVisibilit affect the messages available from already-existing <structname>PGresult</> objects, only subsequently-created ones. + (But see <function>PQresultVerboseErrorMessage</function> if you + want to print a previous error with a different display mode.) </para> </listitem> </varlistentry> @@ -6089,8 +6135,9 @@ PQsetNoticeProcessor(PGconn *conn, receiver function is called. It is passed the message in the form of a <symbol>PGRES_NONFATAL_ERROR</symbol> <structname>PGresult</structname>. (This allows the receiver to extract - individual fields using <function>PQresultErrorField</>, or the complete - preformatted message using <function>PQresultErrorMessage</>.) The same + individual fields using <function>PQresultErrorField</>, or obtain a + complete preformatted message using <function>PQresultErrorMessage</> + or <function>PQresultVerboseErrorMessage</>.) The same void pointer passed to <function>PQsetNoticeReceiver</function> 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 c69a4d5ea4266d46bc4eb58f608c856f44b4fd95..21dd772ca919315b8dcdb22fb979107101bb81c6 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 41937c0bf9a5bdb6bf741df95f4cbb326ab923c9..2621767fd4adba9d1bf745e07181adb9780c9b19 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 3034773972aaf9d3f31512eae9cb25ed2f898311..0b8c62f6ce297106c2602d2b361d29794fcf6ddf 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; } /* diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h index 6bf34b3e99586798ab1c5ff8f424365cd64ccda1..9ca0756c4bfaa7e0ecf256656f5e6f0ee8214515 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 6c9bbf77608bd2d6e4925e55d5317a0c1732da0d..1183323a4456a84184d7fdbedc6e7d00aed928fe 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);