Re: Add schema-qualified relnames in constraint error messages. - Mailing list pgsql-hackers

From Tom Lane
Subject Re: Add schema-qualified relnames in constraint error messages.
Date
Msg-id 10118.1459633308@sss.pgh.pa.us
Whole thread Raw
In response to Re: Add schema-qualified relnames in constraint error messages.  ("Shulgin, Oleksandr" <oleksandr.shulgin@zalando.de>)
Responses Re: Add schema-qualified relnames in constraint error messages.  (Tom Lane <tgl@sss.pgh.pa.us>)
Re: Add schema-qualified relnames in constraint error messages.  (Alex Shulgin <alex.shulgin@gmail.com>)
List pgsql-hackers
"Shulgin, Oleksandr" <oleksandr.shulgin@zalando.de> writes:
> On Mon, Mar 14, 2016 at 7:55 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
>> Yeah, I don't much like that either.  But I don't think we can avoid
>> some refactoring there; as designed, conversion of an error message into
>> user-visible form is too tightly tied to receipt of the message.

> True.  Attached is a v2 which addresses all of the points raised earlier I
> believe.

I took a closer look at what's going on here and realized that actually
it's not that hard to decouple the message-building routine from the
PGconn state, because mostly it works with fields it's extracting out
of the PGresult anyway.  The only piece of information that's lacking
is conn->last_query.  I propose therefore that instead of doing it like
this, we copy last_query into error PGresults.  This is strictly less
added storage requirement than storing the whole verbose message would be,
and it saves time compared to the v2 patch in the typical case where
the application never does ask for an alternately-formatted error message.
Plus we can actually support requests for any variant format, not only
VERBOSE.

Attached is a libpq-portion-only version of a patch doing it this way.
I've not yet looked at the psql part of your patch.

Comments?

            regards, tom lane

diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index 2328d8f..80f7014 100644
*** a/doc/src/sgml/libpq.sgml
--- b/doc/src/sgml/libpq.sgml
*************** char *PQresultErrorMessage(const PGresul
*** 2691,2696 ****
--- 2691,2738 ----
        </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>
*************** PGVerbosity PQsetErrorVerbosity(PGconn *
*** 5582,5587 ****
--- 5624,5631 ----
        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>
*************** PGContextVisibility PQsetErrorContextVis
*** 5622,5627 ****
--- 5666,5673 ----
        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>
*************** PQsetNoticeProcessor(PGconn *conn,
*** 6089,6096 ****
     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
     void pointer passed to <function>PQsetNoticeReceiver</function> is also
     passed.  (This pointer can be used to access application-specific state
     if needed.)
--- 6135,6143 ----
     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 complete
!    preformatted messages 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 c69a4d5..21dd772 100644
*** a/src/interfaces/libpq/exports.txt
--- b/src/interfaces/libpq/exports.txt
*************** PQsslStruct               167
*** 170,172 ****
--- 170,173 ----
  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 41937c0..2621767 100644
*** a/src/interfaces/libpq/fe-exec.c
--- b/src/interfaces/libpq/fe-exec.c
*************** PQmakeEmptyPGresult(PGconn *conn, ExecSt
*** 159,164 ****
--- 159,165 ----
      result->nEvents = 0;
      result->errMsg = NULL;
      result->errFields = NULL;
+     result->errQuery = NULL;
      result->null_field[0] = '\0';
      result->curBlock = NULL;
      result->curOffset = 0;
*************** PQresultErrorMessage(const PGresult *res
*** 2599,2604 ****
--- 2600,2643 ----
  }

  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 3034773..481ee9b 100644
*** a/src/interfaces/libpq/fe-protocol3.c
--- b/src/interfaces/libpq/fe-protocol3.c
*************** pqGetErrorNotice3(PGconn *conn, bool isE
*** 878,886 ****
      PGresult   *res = NULL;
      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
--- 878,883 ----
*************** pqGetErrorNotice3(PGconn *conn, bool isE
*** 905,910 ****
--- 902,909 ----

      /*
       * Read the fields and save into res.
+      *
+      * Also, save the SQLSTATE in conn->last_sqlstate.
       */
      for (;;)
      {
*************** pqGetErrorNotice3(PGconn *conn, bool isE
*** 915,956 ****
          if (pqGets(&workBuf, conn))
              goto fail;
          pqSaveMessageField(res, id, workBuf.data);
      }

      /*
       * 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);
          }
      }
--- 914,1032 ----
          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));
      }

      /*
+      * Save the active query text, if any, into res as well.
+      */
+     if (res && conn->last_query)
+         res->errQuery = pqResultStrdup(res, conn->last_query);
+
+     /*
       * Now build the "overall" error message for PQresultErrorMessage.
       */
      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(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);
          }
      }
*************** pqGetErrorNotice3(PGconn *conn, bool isE
*** 960,966 ****
          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);
--- 1036,1042 ----
          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);
*************** pqGetErrorNotice3(PGconn *conn, bool isE
*** 969,1027 ****
              {
                  /* 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;
--- 1045,1104 ----
              {
                  /* 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;
*************** pqGetErrorNotice3(PGconn *conn, bool isE
*** 1031,1081 ****
          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');
!         }
!     }
!
!     /*
!      * 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;
  }

  /*
--- 1108,1122 ----
          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');
          }
      }
  }

  /*
diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h
index 6bf34b3..9ca0756 100644
*** a/src/interfaces/libpq/libpq-fe.h
--- b/src/interfaces/libpq/libpq-fe.h
*************** extern PGresult *PQfn(PGconn *conn,
*** 463,468 ****
--- 463,471 ----
  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 6c9bbf7..1183323 100644
*** a/src/interfaces/libpq/libpq-int.h
--- b/src/interfaces/libpq/libpq-int.h
*************** struct pg_result
*** 197,202 ****
--- 197,203 ----
       */
      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];
*************** extern char *pqBuildStartupPacket3(PGcon
*** 575,580 ****
--- 576,583 ----
                        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);

pgsql-hackers by date:

Previous
From: Konstantin Knizhnik
Date:
Subject: Re: Batch update of indexes
Next
From: Alvaro Herrera
Date:
Subject: Re: standalone backend PANICs during recovery