From 6428074e27cc20f72c2c9ccec9eeb8686a7978e6 Mon Sep 17 00:00:00 2001
From: Tom Lane <tgl@sss.pgh.pa.us>
Date: Thu, 1 Oct 1998 01:40:26 +0000
Subject: [PATCH] Update libpq to store an error message in PGresult, per
 pgsq-interfaces discussion of 21-Sep.

---
 src/interfaces/libpq/fe-connect.c |   4 +-
 src/interfaces/libpq/fe-exec.c    | 175 ++++++++++++++++++++++--------
 src/interfaces/libpq/fe-lobj.c    |  26 ++++-
 src/interfaces/libpq/libpq-fe.h   |  59 +++++-----
 src/interfaces/libpq/libpq-int.h  |  19 ++--
 src/interfaces/libpq/libpqdll.def |   1 +
 6 files changed, 205 insertions(+), 79 deletions(-)

diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index e21d17f23f7..3a2b16c22d9 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -7,7 +7,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/interfaces/libpq/fe-connect.c,v 1.83 1998/09/20 04:51:10 momjian Exp $
+ *	  $Header: /cvsroot/pgsql/src/interfaces/libpq/fe-connect.c,v 1.84 1998/10/01 01:40:19 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -822,8 +822,8 @@ PQsetenv(PGconn *conn)
 				sprintf(envbuf, "%s=%s", envname, encoding);
 				putenv(envbuf);
 			}
-			PQclear(rtn);
 		}
+		PQclear(rtn);
 		if (!encoding)
 		{						/* this should not happen */
 			sprintf(envbuf, "%s=%s", envname, pg_encoding_to_char(MULTIBYTE));
diff --git a/src/interfaces/libpq/fe-exec.c b/src/interfaces/libpq/fe-exec.c
index 5f889414e19..82c697ef05b 100644
--- a/src/interfaces/libpq/fe-exec.c
+++ b/src/interfaces/libpq/fe-exec.c
@@ -7,7 +7,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/interfaces/libpq/fe-exec.c,v 1.68 1998/09/10 15:18:02 momjian Exp $
+ *	  $Header: /cvsroot/pgsql/src/interfaces/libpq/fe-exec.c,v 1.69 1998/10/01 01:40:21 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -50,7 +50,7 @@ const char *const pgresStatus[] = {
 
 
 static void freeTuple(PGresAttValue *tuple, int numAttributes);
-static void addTuple(PGresult *res, PGresAttValue *tup);
+static int	addTuple(PGresult *res, PGresAttValue *tup);
 static void parseInput(PGconn *conn);
 static int	getRowDescriptions(PGconn *conn);
 static int	getAnotherTuple(PGconn *conn, int binary);
@@ -60,7 +60,9 @@ static int	getNotice(PGconn *conn);
 
 /*
  * PQmakeEmptyPGresult
- *	 returns a newly allocated, initialized PGresult with given status
+ *	 returns a newly allocated, initialized PGresult with given status.
+ *	 If conn is not NULL and status indicates an error, the conn's
+ *	 errorMessage is copied.
  *
  * Note this is exported --- you wouldn't think an application would need
  * to build its own PGresults, but this has proven useful in both libpgtcl
@@ -74,7 +76,7 @@ PQmakeEmptyPGresult(PGconn *conn, ExecStatusType status)
 
 	result = (PGresult *) malloc(sizeof(PGresult));
 
-	result->conn = conn;
+	result->conn = conn;		/* should go away eventually */
 	result->ntups = 0;
 	result->numAttributes = 0;
 	result->attDescs = NULL;
@@ -83,13 +85,45 @@ PQmakeEmptyPGresult(PGconn *conn, ExecStatusType status)
 	result->resultStatus = status;
 	result->cmdStatus[0] = '\0';
 	result->binary = 0;
+	result->errMsg = NULL;
+	if (conn)					/* consider copying conn's errorMessage */
+	{
+		switch (status)
+		{
+			case PGRES_EMPTY_QUERY:
+			case PGRES_COMMAND_OK:
+			case PGRES_TUPLES_OK:
+			case PGRES_COPY_OUT:
+			case PGRES_COPY_IN:
+				/* non-error cases */
+				break;
+			default:
+				pqSetResultError(result, conn->errorMessage);
+				break;
+		}
+	}
 	return result;
 }
 
+/*
+ * pqSetResultError -
+ *		assign a new error message to a PGresult
+ */
+void
+pqSetResultError(PGresult *res, const char *msg)
+{
+	if (!res)
+		return;
+	if (res->errMsg)
+		free(res->errMsg);
+	res->errMsg = NULL;
+	if (msg && *msg)
+		res->errMsg = strdup(msg);
+}
+
 /*
  * PQclear -
  *	  free's the memory associated with a PGresult
- *
  */
 void
 PQclear(PGresult *res)
@@ -118,6 +152,10 @@ PQclear(PGresult *res)
 		free(res->attDescs);
 	}
 
+	/* free the error text */
+	if (res->errMsg)
+		free(res->errMsg);
+
 	/* free the structure itself */
 	free(res);
 }
@@ -164,27 +202,35 @@ pqClearAsyncResult(PGconn *conn)
 /*
  * addTuple
  *	  add a row to the PGresult structure, growing it if necessary
+ *	  Returns TRUE if OK, FALSE if not enough memory to add the row
  */
-static void
+static int
 addTuple(PGresult *res, PGresAttValue *tup)
 {
 	if (res->ntups >= res->tupArrSize)
 	{
-		/* grow the array */
-		res->tupArrSize += TUPARR_GROW_BY;
-
 		/*
-		 * we can use realloc because shallow copying of the structure is
+		 * Try to grow the array.
+		 *
+		 * We can use realloc because shallow copying of the structure is
 		 * okay.  Note that the first time through, res->tuples is NULL.
-		 * realloc is supposed to do the right thing in that case. Also
-		 * note that the positions beyond res->ntups are garbage, not
+		 * realloc is supposed to do the right thing in that case. Also,
+		 * on failure realloc is supposed to return NULL without damaging
+		 * the existing allocation.
+		 * Note that the positions beyond res->ntups are garbage, not
 		 * necessarily NULL.
 		 */
-		res->tuples = (PGresAttValue **)
-			realloc(res->tuples, res->tupArrSize * sizeof(PGresAttValue *));
+		int newSize = res->tupArrSize + TUPARR_GROW_BY;
+		PGresAttValue ** newTuples = (PGresAttValue **)
+			realloc(res->tuples, newSize * sizeof(PGresAttValue *));
+		if (! newTuples)
+			return FALSE;		/* realloc failed */
+		res->tupArrSize = newSize;
+		res->tuples = newTuples;
 	}
 	res->tuples[res->ntups] = tup;
 	res->ntups++;
+	return TRUE;
 }
 
 
@@ -235,7 +281,6 @@ PQsendQuery(PGconn *conn, const char *query)
 	/* initialize async result-accumulation state */
 	conn->result = NULL;
 	conn->curTuple = NULL;
-	conn->asyncErrorMessage[0] = '\0';
 
 	/* send the query to the backend; */
 	/* the frontend-backend protocol uses 'Q' to designate queries */
@@ -270,10 +315,8 @@ PQconsumeInput(PGconn *conn)
 	 * application wants to get rid of a read-select condition. Note that
 	 * we will NOT block waiting for more input.
 	 */
-	if (pqReadData(conn) < 0) {
-		strcpy(conn->asyncErrorMessage, conn->errorMessage);
+	if (pqReadData(conn) < 0)
 		return 0;
-	}
 	/* Parsing of the data waits till later. */
 	return 1;
 }
@@ -360,16 +403,13 @@ parseInput(PGconn *conn)
 					conn->asyncStatus = PGASYNC_READY;
 					break;
 				case 'E':		/* error return */
-					if (pqGets(conn->asyncErrorMessage, ERROR_MSG_LENGTH, conn))
+					if (pqGets(conn->errorMessage, ERROR_MSG_LENGTH, conn))
 						return;
 					/* delete any partially constructed result */
 					pqClearAsyncResult(conn);
-
-					/*
-					 * we leave result NULL while setting
-					 * asyncStatus=READY; this signals an error condition
-					 * to PQgetResult.
-					 */
+					/* and build an error result holding the error message */
+					conn->result = PQmakeEmptyPGresult(conn,
+													   PGRES_FATAL_ERROR);
 					conn->asyncStatus = PGASYNC_READY;
 					break;
 				case 'Z':		/* backend is ready for new query */
@@ -470,15 +510,18 @@ parseInput(PGconn *conn)
 					conn->asyncStatus = PGASYNC_COPY_OUT;
 					break;
 				default:
-					sprintf(conn->asyncErrorMessage,
+					sprintf(conn->errorMessage,
 					"unknown protocol character '%c' read from backend.  "
 					"(The protocol character is the first character the "
-							"backend sends in response to a query it receives).\n",
+					"backend sends in response to a query it receives).\n",
 							id);
 					/* Discard the unexpected message; good idea?? */
 					conn->inStart = conn->inEnd;
 					/* delete any partially constructed result */
 					pqClearAsyncResult(conn);
+					/* and build an error result holding the error message */
+					conn->result = PQmakeEmptyPGresult(conn,
+													   PGRES_FATAL_ERROR);
 					conn->asyncStatus = PGASYNC_READY;
 					return;
 			}					/* switch on protocol character */
@@ -565,7 +608,7 @@ getRowDescriptions(PGconn *conn)
 /*
  * parseInput subroutine to read a 'B' or 'D' (row data) message.
  * We add another tuple to the existing PGresult structure.
- * Returns: 0 if completed message, EOF if not enough data yet.
+ * Returns: 0 if completed message, EOF if error or not enough data yet.
  *
  * Note that if we run out of data, we have to suspend and reprocess
  * the message after more data is received.  We keep a partially constructed
@@ -593,6 +636,8 @@ getAnotherTuple(PGconn *conn, int binary)
 	{
 		conn->curTuple = (PGresAttValue *)
 			malloc(nfields * sizeof(PGresAttValue));
+		if (conn->curTuple == NULL)
+			goto outOfMemory;
 		MemSet((char *) conn->curTuple, 0, nfields * sizeof(PGresAttValue));
 	}
 	tup = conn->curTuple;
@@ -601,9 +646,11 @@ getAnotherTuple(PGconn *conn, int binary)
 	nbytes = (nfields + BYTELEN - 1) / BYTELEN;
 	if (nbytes >= MAX_FIELDS)
 	{
-		sprintf(conn->asyncErrorMessage,
-				"getAnotherTuple() -- null-values bitmap is too large\n");
+		/* Replace partially constructed result with an error result */
 		pqClearAsyncResult(conn);
+		sprintf(conn->errorMessage,
+				"getAnotherTuple() -- null-values bitmap is too large\n");
+		conn->result = PQmakeEmptyPGresult(conn, PGRES_FATAL_ERROR);
 		conn->asyncStatus = PGASYNC_READY;
 		/* Discard the broken message */
 		conn->inStart = conn->inEnd;
@@ -624,7 +671,11 @@ getAnotherTuple(PGconn *conn, int binary)
 		{
 			/* if the field value is absent, make it a null string */
 			if (tup[i].value == NULL)
+			{
 				tup[i].value = strdup("");
+				if (tup[i].value == NULL)
+					goto outOfMemory;
+			}
 			tup[i].len = NULL_LEN;
 		}
 		else
@@ -637,7 +688,11 @@ getAnotherTuple(PGconn *conn, int binary)
 			if (vlen < 0)
 				vlen = 0;
 			if (tup[i].value == NULL)
+			{
 				tup[i].value = (char *) malloc(vlen + 1);
+				if (tup[i].value == NULL)
+					goto outOfMemory;
+			}
 			tup[i].len = vlen;
 			/* read in the value */
 			if (vlen > 0)
@@ -659,10 +714,28 @@ getAnotherTuple(PGconn *conn, int binary)
 	}
 
 	/* Success!  Store the completed tuple in the result */
-	addTuple(conn->result, tup);
+	if (! addTuple(conn->result, tup))
+	{
+		/* Oops, not enough memory to add the tuple to conn->result,
+		 * so must free it ourselves...
+		 */
+		freeTuple(tup, nfields);
+		goto outOfMemory;
+	}
 	/* and reset for a new message */
 	conn->curTuple = NULL;
 	return 0;
+
+outOfMemory:
+	/* Replace partially constructed result with an error result */
+	pqClearAsyncResult(conn);
+	sprintf(conn->errorMessage,
+			"getAnotherTuple() -- out of memory for result\n");
+	conn->result = PQmakeEmptyPGresult(conn, PGRES_FATAL_ERROR);
+	conn->asyncStatus = PGASYNC_READY;
+	/* Discard the failed message --- good idea? */
+	conn->inStart = conn->inEnd;
+	return EOF;
 }
 
 
@@ -725,19 +798,26 @@ PQgetResult(PGconn *conn)
 			res = NULL;			/* query is complete */
 			break;
 		case PGASYNC_READY:
-
 			/*
-			 * conn->result is the PGresult to return, or possibly NULL
-			 * indicating an error. conn->asyncErrorMessage holds the
-			 * errorMessage to return. (We keep it stashed there so that
-			 * other user calls can't overwrite it prematurely.)
+			 * conn->result is the PGresult to return.  If it is NULL
+			 * (which probably shouldn't happen) we assume there is
+			 * an appropriate error message in conn->errorMessage.
 			 */
 			res = conn->result;
-			conn->result = NULL;/* handing over ownership to caller */
+			conn->result = NULL;		/* handing over ownership to caller */
 			conn->curTuple = NULL;		/* just in case */
 			if (!res)
+			{
 				res = PQmakeEmptyPGresult(conn, PGRES_FATAL_ERROR);
-			strcpy(conn->errorMessage, conn->asyncErrorMessage);
+			}
+			else
+			{
+				/* Make sure PQerrorMessage agrees with result; it could be
+				 * that we have done other operations that changed
+				 * errorMessage since the result's error message was saved.
+				 */
+				strcpy(conn->errorMessage, PQresultErrorMessage(res));
+			}
 			/* Set the state back to BUSY, allowing parsing to proceed. */
 			conn->asyncStatus = PGASYNC_BUSY;
 			break;
@@ -763,11 +843,12 @@ PQgetResult(PGconn *conn)
  * PQexec
  *	  send a query to the backend and package up the result in a PGresult
  *
- * if the query failed, return NULL, conn->errorMessage is set to
- * a relevant message
- * if query is successful, a new PGresult is returned
- * the user is responsible for freeing that structure when done with it
- *
+ * If the query was not even sent, return NULL; conn->errorMessage is set to
+ * a relevant message.
+ * If the query was sent, a new PGresult is returned (which could indicate
+ * either success or failure).
+ * The user is responsible for freeing the PGresult via PQclear()
+ * when done with it.
  */
 
 PGresult   *
@@ -1312,6 +1393,14 @@ PQresultStatus(PGresult *res)
 	return res->resultStatus;
 }
 
+const char *
+PQresultErrorMessage(PGresult *res)
+{
+	if (!res || !res->errMsg)
+		return "";
+	return res->errMsg;
+}
+
 int
 PQntuples(PGresult *res)
 {
diff --git a/src/interfaces/libpq/fe-lobj.c b/src/interfaces/libpq/fe-lobj.c
index 190cab18106..19c7770f3f1 100644
--- a/src/interfaces/libpq/fe-lobj.c
+++ b/src/interfaces/libpq/fe-lobj.c
@@ -7,7 +7,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/interfaces/libpq/fe-lobj.c,v 1.16 1998/09/01 04:40:07 momjian Exp $
+ *	  $Header: /cvsroot/pgsql/src/interfaces/libpq/fe-lobj.c,v 1.17 1998/10/01 01:40:22 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -76,7 +76,10 @@ lo_open(PGconn *conn, Oid lobjId, int mode)
 		return fd;
 	}
 	else
+	{
+		PQclear(res);
 		return -1;
+	}
 }
 
 /*
@@ -111,7 +114,10 @@ lo_close(PGconn *conn, int fd)
 		return retval;
 	}
 	else
+	{
+		PQclear(res);
 		return -1;
+	}
 }
 
 /*
@@ -151,7 +157,10 @@ lo_read(PGconn *conn, int fd, char *buf, int len)
 		return result_len;
 	}
 	else
+	{
+		PQclear(res);
 		return -1;
+	}
 }
 
 /*
@@ -192,7 +201,10 @@ lo_write(PGconn *conn, int fd, char *buf, int len)
 		return retval;
 	}
 	else
+	{
+		PQclear(res);
 		return -1;
+	}
 }
 
 /*
@@ -236,7 +248,10 @@ lo_lseek(PGconn *conn, int fd, int offset, int whence)
 		return retval;
 	}
 	else
+	{
+		PQclear(res);
 		return -1;
+	}
 }
 
 /*
@@ -273,7 +288,10 @@ lo_creat(PGconn *conn, int mode)
 		return (Oid) retval;
 	}
 	else
+	{
+		PQclear(res);
 		return InvalidOid;
+	}
 }
 
 
@@ -309,7 +327,10 @@ lo_tell(PGconn *conn, int fd)
 		return retval;
 	}
 	else
+	{
+		PQclear(res);
 		return -1;
+	}
 }
 
 /*
@@ -344,7 +365,10 @@ lo_unlink(PGconn *conn, Oid lobjId)
 		return retval;
 	}
 	else
+	{
+		PQclear(res);
 		return -1;
+	}
 }
 
 /*
diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h
index 8f090352635..13ce50b873d 100644
--- a/src/interfaces/libpq/libpq-fe.h
+++ b/src/interfaces/libpq/libpq-fe.h
@@ -6,7 +6,7 @@
  *
  * Copyright (c) 1994, Regents of the University of California
  *
- * $Id: libpq-fe.h,v 1.43 1998/09/18 16:46:06 momjian Exp $
+ * $Id: libpq-fe.h,v 1.44 1998/10/01 01:40:23 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -67,6 +67,8 @@ extern		"C"
 /* PGnotify represents the occurrence of a NOTIFY message.
  * Ideally this would be an opaque typedef, but it's so simple that it's
  * unlikely to change.
+ * NOTE: in Postgres 6.4 and later, the be_pid is the notifying backend's,
+ * whereas in earlier versions it was always your own backend's PID.
  */
 	typedef struct pgNotify
 	{
@@ -78,7 +80,7 @@ extern		"C"
 /* PQnoticeProcessor is the function type for the notice-message callback.
  */
 
-typedef void (*PQnoticeProcessor) (void * arg, const char * message);
+	typedef void (*PQnoticeProcessor) (void * arg, const char * message);
 
 /* Print options for PQprint() */
   
@@ -219,15 +221,16 @@ typedef void (*PQnoticeProcessor) (void * arg, const char * message);
 	 * use
 	 */
 	extern PGresult *PQfn(PGconn *conn,
-									  int fnid,
-									  int *result_buf,
-									  int *result_len,
-									  int result_is_int,
-									  PQArgBlock *args,
-									  int nargs);
+						  int fnid,
+						  int *result_buf,
+						  int *result_len,
+						  int result_is_int,
+						  PQArgBlock *args,
+						  int nargs);
 
 	/* Accessor functions for PGresult objects */
 	extern ExecStatusType PQresultStatus(PGresult *res);
+	extern const char *PQresultErrorMessage(PGresult *res);
 	extern int	PQntuples(PGresult *res);
 	extern int	PQnfields(PGresult *res);
 	extern int	PQbinaryTuples(PGresult *res);
@@ -246,35 +249,39 @@ typedef void (*PQnoticeProcessor) (void * arg, const char * message);
 	/* Delete a PGresult */
 	extern void PQclear(PGresult *res);
 
-	/* Make an empty PGresult with given status (some apps find this useful) */
+	/* Make an empty PGresult with given status (some apps find this useful).
+	 * If conn is not NULL and status indicates an error, the conn's
+	 * errorMessage is copied.
+	 */
 	extern PGresult * PQmakeEmptyPGresult(PGconn *conn, ExecStatusType status);
 
 /* === in fe-print.c === */
 
-	extern void PQprint(FILE *fout,		/* output stream */
-									PGresult *res,
-									PQprintOpt *ps);	/* option structure */
+	extern void PQprint(FILE *fout,			/* output stream */
+						PGresult *res,
+						PQprintOpt *ps);	/* option structure */
 
 	/*
 	 * PQdisplayTuples() is a better version of PQprintTuples(), but both
 	 * are obsoleted by PQprint().
 	 */
 	extern void PQdisplayTuples(PGresult *res,
-											FILE *fp,	/* where to send the
-														 * output */
-											int fillAlign,		/* pad the fields with
-																 * spaces */
-											const char *fieldSep,		/* field separator */
-											int printHeader,	/* display headers? */
-											int quiet);
+								FILE *fp,			/* where to send the
+													 * output */
+								int fillAlign,		/* pad the fields with
+													 * spaces */
+								const char *fieldSep,	/* field separator */
+								int printHeader,	/* display headers? */
+								int quiet);
+
 	extern void PQprintTuples(PGresult *res,
-										  FILE *fout,	/* output stream */
-										  int printAttName,		/* print attribute names
-																 * or not */
-										  int terseOutput,		/* delimiter bars or
-																 * not? */
-										  int width);	/* width of column, if
-														 * 0, use variable width */
+							  FILE *fout,			/* output stream */
+							  int printAttName,		/* print attribute names
+													 * or not */
+							  int terseOutput,		/* delimiter bars or
+													 * not? */
+							  int width);			/* width of column, if
+													 * 0, use variable width */
 
 #ifdef MULTIBYTE
 	extern int	PQmblen(unsigned char *s);
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index 4c877de2d2e..e05ea4bebff 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -11,7 +11,7 @@
  *
  * Copyright (c) 1994, Regents of the University of California
  *
- * $Id: libpq-int.h,v 1.3 1998/09/03 02:10:53 momjian Exp $
+ * $Id: libpq-int.h,v 1.4 1998/10/01 01:40:25 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -78,7 +78,7 @@
 		char	   *value;		/* actual value */
 	} PGresAttValue;
 
- struct pg_result
+	struct pg_result
 	{
 		int			ntups;
 		int			numAttributes;
@@ -91,7 +91,15 @@
 												 * last insert query */
 		int			binary;		/* binary tuple values if binary == 1,
 								 * otherwise ASCII */
+		/* NOTE: conn is kept here only for the temporary convenience of
+		 * applications that rely on it being here.  It will go away in a
+		 * future release, because relying on it is a bad idea --- what if
+		 * the PGresult has outlived the PGconn?  About the only thing it was
+		 * really good for was fetching the errorMessage, and we stash that
+		 * here now anyway.
+		 */
 		PGconn		*conn;		/* connection we did the query on */
+		char		*errMsg;	/* error message, or NULL if no error */
 	};
 
 /* PGAsyncStatusType defines the state of the query-execution state machine */
@@ -174,12 +182,8 @@
 		PGresult		*result;	/* result being constructed */
 		PGresAttValue	*curTuple;	/* tuple currently being read */
 
-		/* Message space.  Placed last for code-size reasons.
-		 * errorMessage is the message last returned to the application.
-		 * When asyncStatus=READY, asyncErrorMessage is the pending message
-		 * that will be put in errorMessage by PQgetResult. */
+		/* Message space.  Placed last for code-size reasons. */
 		char		errorMessage[ERROR_MSG_LENGTH];
-		char		asyncErrorMessage[ERROR_MSG_LENGTH];
 	};
 
 /* ----------------
@@ -197,6 +201,7 @@ extern int	pqPacketSend(PGconn *conn, const char *buf, size_t len);
 
 /* === in fe-exec.c === */
 
+extern void pqSetResultError(PGresult *res, const char *msg);
 extern void pqClearAsyncResult(PGconn *conn);
 
 /* === in fe-misc.c === */
diff --git a/src/interfaces/libpq/libpqdll.def b/src/interfaces/libpq/libpqdll.def
index fee6f217d60..a7c9b28ef80 100644
--- a/src/interfaces/libpq/libpqdll.def
+++ b/src/interfaces/libpq/libpqdll.def
@@ -63,3 +63,4 @@ EXPORTS
 	lo_unlink		@ 60
 	lo_import		@ 61
 	lo_export		@ 62
+	PQresultErrorMessage	@ 63
-- 
GitLab