Add libpq support for recreating an error message with different verbosity.
authorTom Lane
Sun, 3 Apr 2016 16:24:54 +0000 (12:24 -0400)
committerTom Lane
Sun, 3 Apr 2016 16:24:54 +0000 (12:24 -0400)
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

doc/src/sgml/libpq.sgml
src/interfaces/libpq/exports.txt
src/interfaces/libpq/fe-exec.c
src/interfaces/libpq/fe-protocol3.c
src/interfaces/libpq/libpq-fe.h
src/interfaces/libpq/libpq-int.h

index 2328d8f5f21224a68b4dc4bf823159b8348920d2..3829a1400d93720812e9eb55adadda7326d4f106 100644 (file)
@@ -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.)
index c69a4d5ea4266d46bc4eb58f608c856f44b4fd95..21dd772ca919315b8dcdb22fb979107101bb81c6 100644 (file)
@@ -170,3 +170,4 @@ PQsslStruct               167
 PQsslAttributeNames       168
 PQsslAttribute            169
 PQsetErrorContextVisibility 170
+PQresultVerboseErrorMessage 171
index 41937c0bf9a5bdb6bf741df95f4cbb326ab923c9..2621767fd4adba9d1bf745e07181adb9780c9b19 100644 (file)
@@ -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)
 {
index 3034773972aaf9d3f31512eae9cb25ed2f898311..0b8c62f6ce297106c2602d2b361d29794fcf6ddf 100644 (file)
@@ -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;
 }
 
 /*
index 6bf34b3e99586798ab1c5ff8f424365cd64ccda1..9ca0756c4bfaa7e0ecf256656f5e6f0ee8214515 100644 (file)
@@ -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);
index 6c9bbf77608bd2d6e4925e55d5317a0c1732da0d..1183323a4456a84184d7fdbedc6e7d00aed928fe 100644 (file)
@@ -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);