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);