diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/interfaces/libpq/exports.txt | 1 | ||||
-rw-r--r-- | src/interfaces/libpq/fe-exec.c | 39 | ||||
-rw-r--r-- | src/interfaces/libpq/fe-protocol3.c | 197 | ||||
-rw-r--r-- | src/interfaces/libpq/libpq-fe.h | 3 | ||||
-rw-r--r-- | src/interfaces/libpq/libpq-int.h | 3 |
5 files changed, 168 insertions, 75 deletions
diff --git a/src/interfaces/libpq/exports.txt b/src/interfaces/libpq/exports.txt index c69a4d5ea42..21dd772ca91 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 41937c0bf9a..2621767fd4a 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; @@ -2599,6 +2600,44 @@ PQresultErrorMessage(const PGresult *res) } 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) { PGMessageField *pfield; diff --git a/src/interfaces/libpq/fe-protocol3.c b/src/interfaces/libpq/fe-protocol3.c index 3034773972a..0b8c62f6ce2 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 6bf34b3e995..9ca0756c4bf 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 6c9bbf77608..1183323a445 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); |