From ab5cafa5d3f47439d8f65516bc5b88ad04117621 Mon Sep 17 00:00:00 2001
From: Tom Lane <tgl@sss.pgh.pa.us>
Date: Tue, 31 Aug 1999 01:37:37 +0000
Subject: [PATCH] Update frontend libpq to remove limits on query lengths,
 error/notice message lengths, and number of fields per tuple.  Add
 pqexpbuffer.c/.h, a frontend version of backend's stringinfo module. This is
 first step in applying Mike Ansley's long-query patches, even though he
 didn't do any of these particular changes...

---
 src/interfaces/libpq/Makefile.in   |   5 +-
 src/interfaces/libpq/fe-auth.c     |   4 +-
 src/interfaces/libpq/fe-connect.c  | 197 +++++++-------
 src/interfaces/libpq/fe-exec.c     | 398 ++++++++++++++++++-----------
 src/interfaces/libpq/fe-lobj.c     |  77 +++---
 src/interfaces/libpq/fe-misc.c     |  77 +++---
 src/interfaces/libpq/fe-print.c    | 117 ++++-----
 src/interfaces/libpq/libpq-int.h   |  29 +--
 src/interfaces/libpq/pqexpbuffer.c | 254 ++++++++++++++++++
 src/interfaces/libpq/pqexpbuffer.h | 169 ++++++++++++
 src/interfaces/libpq/win32.mak     |   4 +-
 11 files changed, 939 insertions(+), 392 deletions(-)
 create mode 100644 src/interfaces/libpq/pqexpbuffer.c
 create mode 100644 src/interfaces/libpq/pqexpbuffer.h

diff --git a/src/interfaces/libpq/Makefile.in b/src/interfaces/libpq/Makefile.in
index 73f333cae12..ec955dcd4bc 100644
--- a/src/interfaces/libpq/Makefile.in
+++ b/src/interfaces/libpq/Makefile.in
@@ -6,7 +6,7 @@
 # Copyright (c) 1994, Regents of the University of California
 #
 # IDENTIFICATION
-#    $Header: /cvsroot/pgsql/src/interfaces/libpq/Attic/Makefile.in,v 1.46 1999/06/30 23:57:25 tgl Exp $
+#    $Header: /cvsroot/pgsql/src/interfaces/libpq/Attic/Makefile.in,v 1.47 1999/08/31 01:37:36 tgl Exp $
 #
 #-------------------------------------------------------------------------
 
@@ -28,7 +28,7 @@ CFLAGS+= $(MBFLAGS)
 endif
 
 OBJS= fe-auth.o fe-connect.o fe-exec.o fe-misc.o fe-print.o fe-lobj.o \
-      dllist.o pqsignal.o
+      pqexpbuffer.o dllist.o pqsignal.o
 
 ifdef MULTIBYTE
 OBJS+= common.o wchar.o conv.o big5.o
@@ -80,6 +80,7 @@ install-headers: libpq-fe.h libpq-int.h
 	@if [ ! -d $(HEADERDIR) ]; then mkdir $(HEADERDIR); fi
 	$(INSTALL) $(INSTLOPTS) libpq-fe.h $(HEADERDIR)/libpq-fe.h
 	$(INSTALL) $(INSTLOPTS) libpq-int.h $(HEADERDIR)/libpq-int.h
+	$(INSTALL) $(INSTLOPTS) pqexpbuffer.h $(HEADERDIR)/pqexpbuffer.h
 
 
 .PHONY: clean
diff --git a/src/interfaces/libpq/fe-auth.c b/src/interfaces/libpq/fe-auth.c
index f72f76b4080..574d78c25d5 100644
--- a/src/interfaces/libpq/fe-auth.c
+++ b/src/interfaces/libpq/fe-auth.c
@@ -5,9 +5,11 @@
  *
  * Copyright (c) 1994, Regents of the University of California
  *
+ * NOTE: the error message strings returned by this module must not
+ * exceed INITIAL_EXPBUFFER_SIZE (currently 256 bytes).
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/interfaces/libpq/fe-auth.c,v 1.32 1999/07/19 06:25:38 momjian Exp $
+ *	  $Header: /cvsroot/pgsql/src/interfaces/libpq/fe-auth.c,v 1.33 1999/08/31 01:37:36 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index 4edb4e8e596..4e86a8db91e 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.101 1999/07/19 06:25:38 momjian Exp $
+ *	  $Header: /cvsroot/pgsql/src/interfaces/libpq/fe-connect.c,v 1.102 1999/08/31 01:37:36 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -44,7 +44,7 @@ static ConnStatusType connectDB(PGconn *conn);
 static PGconn *makeEmptyPGconn(void);
 static void freePGconn(PGconn *conn);
 static void closePGconn(PGconn *conn);
-static int	conninfo_parse(const char *conninfo, char *errorMessage);
+static int	conninfo_parse(const char *conninfo, PQExpBuffer errorMessage);
 static char *conninfo_getval(char *keyword);
 static void conninfo_free(void);
 static void defaultNoticeProcessor(void *arg, const char *message);
@@ -178,7 +178,7 @@ PQconnectdb(const char *conninfo)
 	 * Parse the conninfo string and save settings in conn structure
 	 * ----------
 	 */
-	if (conninfo_parse(conninfo, conn->errorMessage) < 0)
+	if (conninfo_parse(conninfo, &conn->errorMessage) < 0)
 	{
 		conn->status = CONNECTION_BAD;
 		conninfo_free();
@@ -226,9 +226,11 @@ PQconnectdb(const char *conninfo)
 PQconninfoOption *
 PQconndefaults(void)
 {
-	char		errorMessage[ERROR_MSG_LENGTH];
+	PQExpBufferData  errorBuf;
 
-	conninfo_parse("", errorMessage);
+	initPQExpBuffer(&errorBuf);
+	conninfo_parse("", &errorBuf);
+	termPQExpBuffer(&errorBuf);
 	return PQconninfoOptions;
 }
 
@@ -328,13 +330,17 @@ PQsetdbLogin(const char *pghost, const char *pgport, const char *pgoptions, cons
 	else if ((tmp = getenv("PGUSER")) != NULL)
 		conn->pguser = strdup(tmp);
 	else
-		conn->pguser = fe_getauthname(conn->errorMessage);
+	{
+		/* fe-auth.c has not been fixed to support PQExpBuffers, so: */
+		conn->pguser = fe_getauthname(conn->errorMessage.data);
+		conn->errorMessage.len = strlen(conn->errorMessage.data);
+	}
 
 	if (conn->pguser == NULL)
 	{
 		error = TRUE;
-		sprintf(conn->errorMessage,
-				"FATAL: PQsetdbLogin(): Unable to determine a Postgres username!\n");
+		printfPQExpBuffer(&conn->errorMessage,
+						  "FATAL: PQsetdbLogin(): Unable to determine a Postgres username!\n");
 	}
 
 	if (pwd)
@@ -469,8 +475,8 @@ update_db_info(PGconn *conn)
 				conn->pghost = NULL;
 				if (strcmp(old + offset, "localhost") != 0)
 				{
-					(void) sprintf(conn->errorMessage,
-								   "connectDB() -- non-tcp access only possible on localhost\n");
+					printfPQExpBuffer(&conn->errorMessage,
+									  "connectDB() -- non-tcp access only possible on localhost\n");
 					return 1;
 				}
 			}
@@ -533,9 +539,9 @@ connectDB(PGconn *conn)
 		hp = gethostbyname(conn->pghost);
 		if ((hp == NULL) || (hp->h_addrtype != AF_INET))
 		{
-			(void) sprintf(conn->errorMessage,
-						   "connectDB() --  unknown hostname: %s\n",
-						   conn->pghost);
+			printfPQExpBuffer(&conn->errorMessage,
+							  "connectDB() --  unknown hostname: %s\n",
+							  conn->pghost);
 			goto connect_errReturn;
 		}
 		family = AF_INET;
@@ -567,21 +573,21 @@ connectDB(PGconn *conn)
 	/* Connect to the server  */
 	if ((conn->sock = socket(family, SOCK_STREAM, 0)) < 0)
 	{
-		(void) sprintf(conn->errorMessage,
-					   "connectDB() -- socket() failed: errno=%d\n%s\n",
-					   errno, strerror(errno));
+		printfPQExpBuffer(&conn->errorMessage,
+						  "connectDB() -- socket() failed: errno=%d\n%s\n",
+						  errno, strerror(errno));
 		goto connect_errReturn;
 	}
 	if (connect(conn->sock, &conn->raddr.sa, conn->raddr_len) < 0)
 	{
-		(void) sprintf(conn->errorMessage,
-					   "connectDB() -- connect() failed: %s\n"
-					   "Is the postmaster running%s at '%s' and accepting connections on %s '%s'?\n",
-					   strerror(errno),
-					   (family == AF_INET) ? " (with -i)" : "",
-					   conn->pghost ? conn->pghost : "localhost",
-					 (family == AF_INET) ? "TCP/IP port" : "Unix socket",
-					   conn->pgport);
+		printfPQExpBuffer(&conn->errorMessage,
+						  "connectDB() -- connect() failed: %s\n"
+						  "Is the postmaster running%s at '%s' and accepting connections on %s '%s'?\n",
+						  strerror(errno),
+						  (family == AF_INET) ? " (with -i)" : "",
+						  conn->pghost ? conn->pghost : "localhost",
+						  (family == AF_INET) ? "TCP/IP port" : "Unix socket",
+						  conn->pgport);
 		goto connect_errReturn;
 	}
 
@@ -596,9 +602,9 @@ connectDB(PGconn *conn)
 	if (ioctlsocket(conn->sock, FIONBIO, &on) != 0)
 #endif
 	{
-		(void) sprintf(conn->errorMessage,
-					   "connectDB() -- fcntl() failed: errno=%d\n%s\n",
-					   errno, strerror(errno));
+		printfPQExpBuffer(&conn->errorMessage,
+						  "connectDB() -- fcntl() failed: errno=%d\n%s\n",
+						  errno, strerror(errno));
 		goto connect_errReturn;
 	}
 
@@ -609,8 +615,8 @@ connectDB(PGconn *conn)
 		pe = getprotobyname("TCP");
 		if (pe == NULL)
 		{
-			(void) sprintf(conn->errorMessage,
-						   "connectDB(): getprotobyname failed\n");
+			printfPQExpBuffer(&conn->errorMessage,
+							  "connectDB(): getprotobyname failed\n");
 			goto connect_errReturn;
 		}
 		if (setsockopt(conn->sock, pe->p_proto, TCP_NODELAY,
@@ -620,9 +626,9 @@ connectDB(PGconn *conn)
 					   &on,
 					   sizeof(on)) < 0)
 		{
-			(void) sprintf(conn->errorMessage,
-					  "connectDB() -- setsockopt failed: errno=%d\n%s\n",
-						   errno, strerror(errno));
+			printfPQExpBuffer(&conn->errorMessage,
+							  "connectDB() -- setsockopt failed: errno=%d\n%s\n",
+							  errno, strerror(errno));
 #ifdef WIN32
 			printf("Winsock error: %i\n", WSAGetLastError());
 #endif
@@ -634,9 +640,9 @@ connectDB(PGconn *conn)
 	laddrlen = sizeof(conn->laddr);
 	if (getsockname(conn->sock, &conn->laddr.sa, &laddrlen) < 0)
 	{
-		(void) sprintf(conn->errorMessage,
-				   "connectDB() -- getsockname() failed: errno=%d\n%s\n",
-					   errno, strerror(errno));
+		printfPQExpBuffer(&conn->errorMessage,
+						  "connectDB() -- getsockname() failed: errno=%d\n%s\n",
+						  errno, strerror(errno));
 		goto connect_errReturn;
 	}
 
@@ -648,9 +654,9 @@ connectDB(PGconn *conn)
 
 	if (pqPacketSend(conn, (char *) &sp, sizeof(StartupPacket)) != STATUS_OK)
 	{
-		sprintf(conn->errorMessage,
-		  "connectDB() --  couldn't send startup packet: errno=%d\n%s\n",
-				errno, strerror(errno));
+		printfPQExpBuffer(&conn->errorMessage,
+						  "connectDB() --  couldn't send startup packet: errno=%d\n%s\n",
+						  errno, strerror(errno));
 		goto connect_errReturn;
 	}
 
@@ -681,7 +687,7 @@ connectDB(PGconn *conn)
 		/* Handle errors. */
 		if (beresp == 'E')
 		{
-			if (pqGets(conn->errorMessage, sizeof(conn->errorMessage), conn))
+			if (pqGets(&conn->errorMessage, conn))
 				continue;
 			goto connect_errReturn;
 		}
@@ -689,8 +695,8 @@ connectDB(PGconn *conn)
 		/* Otherwise it should be an authentication request. */
 		if (beresp != 'R')
 		{
-			(void) sprintf(conn->errorMessage,
-					 "connectDB() -- expected authentication request\n");
+			printfPQExpBuffer(&conn->errorMessage,
+							  "connectDB() -- expected authentication request\n");
 			goto connect_errReturn;
 		}
 
@@ -709,9 +715,14 @@ connectDB(PGconn *conn)
 		conn->inStart = conn->inCursor;
 
 		/* Respond to the request if necessary. */
+		/* fe-auth.c has not been fixed to support PQExpBuffers, so: */
 		if (fe_sendauth(areq, conn, conn->pghost, conn->pgpass,
-						conn->errorMessage) != STATUS_OK)
+						conn->errorMessage.data) != STATUS_OK)
+		{
+			conn->errorMessage.len = strlen(conn->errorMessage.data);
 			goto connect_errReturn;
+		}
+
 		if (pqFlush(conn))
 			goto connect_errReturn;
 
@@ -737,36 +748,12 @@ connectDB(PGconn *conn)
 	if (res)
 	{
 		if (res->resultStatus != PGRES_FATAL_ERROR)
-			sprintf(conn->errorMessage,
-					"connectDB() -- unexpected message during startup\n");
+			printfPQExpBuffer(&conn->errorMessage,
+							  "connectDB() -- unexpected message during startup\n");
 		PQclear(res);
 		goto connect_errReturn;
 	}
 
-	/*
-	 * Given the new protocol that sends a ReadyForQuery message after
-	 * successful backend startup, it should no longer be necessary to
-	 * send an empty query to test for startup.
-	 */
-
-#ifdef NOT_USED
-
-	/*
-	 * Send a blank query to make sure everything works; in particular,
-	 * that the database exists.
-	 */
-	res = PQexec(conn, " ");
-	if (res == NULL || res->resultStatus != PGRES_EMPTY_QUERY)
-	{
-		/* PQexec has put error message in conn->errorMessage */
-		closePGconn(conn);
-		PQclear(res);
-		goto connect_errReturn;
-	}
-	PQclear(res);
-
-#endif
-
 	/*
 	 * Post-connection housekeeping. Send environment variables to server
 	 */
@@ -870,11 +857,27 @@ makeEmptyPGconn(void)
 	conn->asyncStatus = PGASYNC_IDLE;
 	conn->notifyList = DLNewList();
 	conn->sock = -1;
-	conn->inBufSize = PQ_BUFFER_SIZE;
+	/*
+	 * The output buffer size is set to 8K, which is the usual size of pipe
+	 * buffers on Unix systems.  That way, when we are sending a large
+	 * amount of data, we avoid incurring extra kernel context swaps for
+	 * partial bufferloads.  Note that we currently don't ever enlarge
+	 * the output buffer.
+	 *
+	 * With the same goal of minimizing context swaps, the input buffer will
+	 * be enlarged anytime it has less than 8K free, so we initially allocate
+	 * twice that.
+	 */
+	conn->inBufSize = 16 * 1024;
 	conn->inBuffer = (char *) malloc(conn->inBufSize);
-	conn->outBufSize = PQ_BUFFER_SIZE;
+	conn->outBufSize = 8 * 1024;
 	conn->outBuffer = (char *) malloc(conn->outBufSize);
-	if (conn->inBuffer == NULL || conn->outBuffer == NULL)
+	initPQExpBuffer(&conn->errorMessage);
+	initPQExpBuffer(&conn->workBuffer);
+	if (conn->inBuffer == NULL ||
+		conn->outBuffer == NULL ||
+		conn->errorMessage.data == NULL ||
+		conn->workBuffer.data == NULL)
 	{
 		freePGconn(conn);
 		conn = NULL;
@@ -922,6 +925,8 @@ freePGconn(PGconn *conn)
 		free(conn->inBuffer);
 	if (conn->outBuffer)
 		free(conn->outBuffer);
+	termPQExpBuffer(&conn->errorMessage);
+	termPQExpBuffer(&conn->workBuffer);
 	free(conn);
 }
 
@@ -1002,16 +1007,24 @@ PQreset(PGconn *conn)
  * PQrequestCancel: attempt to request cancellation of the current operation.
  *
  * The return value is TRUE if the cancel request was successfully
- * dispatched, FALSE if not (in which case errorMessage is set).
+ * dispatched, FALSE if not (in which case conn->errorMessage is set).
  * Note: successful dispatch is no guarantee that there will be any effect at
  * the backend.  The application must read the operation result as usual.
  *
+ * XXX it was a bad idea to have the error message returned in
+ * conn->errorMessage, since it could overwrite a message already there.
+ * Would be better to return it in a char array passed by the caller.
+ *
  * CAUTION: we want this routine to be safely callable from a signal handler
  * (for example, an application might want to call it in a SIGINT handler).
  * This means we cannot use any C library routine that might be non-reentrant.
  * malloc/free are often non-reentrant, and anything that might call them is
  * just as dangerous.  We avoid sprintf here for that reason.  Building up
  * error messages with strcpy/strcat is tedious but should be quite safe.
+ *
+ * NOTE: this routine must not generate any error message longer than
+ * INITIAL_EXPBUFFER_SIZE (currently 256), since we dare not try to
+ * expand conn->errorMessage!
  */
 
 int
@@ -1030,8 +1043,9 @@ PQrequestCancel(PGconn *conn)
 
 	if (conn->sock < 0)
 	{
-		strcpy(conn->errorMessage,
+		strcpy(conn->errorMessage.data,
 			   "PQrequestCancel() -- connection is not open\n");
+		conn->errorMessage.len = strlen(conn->errorMessage.data);
 		return FALSE;
 	}
 
@@ -1041,12 +1055,14 @@ PQrequestCancel(PGconn *conn)
 	 */
 	if ((tmpsock = socket(conn->raddr.sa.sa_family, SOCK_STREAM, 0)) < 0)
 	{
-		strcpy(conn->errorMessage, "PQrequestCancel() -- socket() failed: ");
+		strcpy(conn->errorMessage.data,
+			   "PQrequestCancel() -- socket() failed: ");
 		goto cancel_errReturn;
 	}
 	if (connect(tmpsock, &conn->raddr.sa, conn->raddr_len) < 0)
 	{
-		strcpy(conn->errorMessage, "PQrequestCancel() -- connect() failed: ");
+		strcpy(conn->errorMessage.data,
+			   "PQrequestCancel() -- connect() failed: ");
 		goto cancel_errReturn;
 	}
 
@@ -1063,7 +1079,8 @@ PQrequestCancel(PGconn *conn)
 
 	if (send(tmpsock, (char *) &crp, sizeof(crp), 0) != (int) sizeof(crp))
 	{
-		strcpy(conn->errorMessage, "PQrequestCancel() -- send() failed: ");
+		strcpy(conn->errorMessage.data,
+			   "PQrequestCancel() -- send() failed: ");
 		goto cancel_errReturn;
 	}
 
@@ -1077,8 +1094,9 @@ PQrequestCancel(PGconn *conn)
 	return TRUE;
 
 cancel_errReturn:
-	strcat(conn->errorMessage, strerror(errno));
-	strcat(conn->errorMessage, "\n");
+	strcat(conn->errorMessage.data, strerror(errno));
+	strcat(conn->errorMessage.data, "\n");
+	conn->errorMessage.len = strlen(conn->errorMessage.data);
 	if (tmpsock >= 0)
 	{
 #ifdef WIN32
@@ -1123,7 +1141,7 @@ pqPacketSend(PGconn *conn, const char *buf, size_t len)
  * ----------------
  */
 static int
-conninfo_parse(const char *conninfo, char *errorMessage)
+conninfo_parse(const char *conninfo, PQExpBuffer errorMessage)
 {
 	char	   *pname;
 	char	   *pval;
@@ -1132,13 +1150,13 @@ conninfo_parse(const char *conninfo, char *errorMessage)
 	char	   *cp;
 	char	   *cp2;
 	PQconninfoOption *option;
-	char		errortmp[ERROR_MSG_LENGTH];
+	char		errortmp[INITIAL_EXPBUFFER_SIZE];
 
 	conninfo_free();
 
 	if ((buf = strdup(conninfo)) == NULL)
 	{
-		strcpy(errorMessage,
+		printfPQExpBuffer(errorMessage,
 		  "FATAL: cannot allocate memory for copy of conninfo string\n");
 		return -1;
 	}
@@ -1176,9 +1194,9 @@ conninfo_parse(const char *conninfo, char *errorMessage)
 		/* Check that there is a following '=' */
 		if (*cp != '=')
 		{
-			sprintf(errorMessage,
-			"ERROR: PQconnectdb() - Missing '=' after '%s' in conninfo\n",
-					pname);
+			printfPQExpBuffer(errorMessage,
+				"ERROR: PQconnectdb() - Missing '=' after '%s' in conninfo\n",
+							  pname);
 			free(buf);
 			return -1;
 		}
@@ -1223,7 +1241,7 @@ conninfo_parse(const char *conninfo, char *errorMessage)
 			{
 				if (*cp == '\0')
 				{
-					sprintf(errorMessage,
+					printfPQExpBuffer(errorMessage,
 							"ERROR: PQconnectdb() - unterminated quoted string in conninfo\n");
 					free(buf);
 					return -1;
@@ -1257,9 +1275,9 @@ conninfo_parse(const char *conninfo, char *errorMessage)
 		}
 		if (option->keyword == NULL)
 		{
-			sprintf(errorMessage,
-					"ERROR: PQconnectdb() - unknown option '%s'\n",
-					pname);
+			printfPQExpBuffer(errorMessage,
+							  "ERROR: PQconnectdb() - unknown option '%s'\n",
+							  pname);
 			free(buf);
 			return -1;
 		}
@@ -1314,6 +1332,7 @@ conninfo_parse(const char *conninfo, char *errorMessage)
 		if (!strcmp(option->keyword, "user"))
 		{
 			option->val = fe_getauthname(errortmp);
+			/* note any error message is thrown away */
 			continue;
 		}
 
@@ -1436,7 +1455,7 @@ PQerrorMessage(PGconn *conn)
 
 	if (!conn)
 		return noConn;
-	return conn->errorMessage;
+	return conn->errorMessage.data;
 }
 
 int
diff --git a/src/interfaces/libpq/fe-exec.c b/src/interfaces/libpq/fe-exec.c
index 01613e0db84..24fe9860ebb 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.84 1999/07/19 06:25:39 momjian Exp $
+ *	  $Header: /cvsroot/pgsql/src/interfaces/libpq/fe-exec.c,v 1.85 1999/08/31 01:37:36 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -42,6 +42,9 @@ const char *const pgresStatus[] = {
 	((*(conn)->noticeHook) ((conn)->noticeArg, (message)))
 
 
+static void pqCatenateResultError(PGresult *res, const char *msg);
+static void saveErrorResult(PGconn *conn);
+static PGresult *prepareAsyncResult(PGconn *conn);
 static int	addTuple(PGresult *res, PGresAttValue *tup);
 static void parseInput(PGconn *conn);
 static void handleSendFailure(PGconn *conn);
@@ -158,7 +161,7 @@ PQmakeEmptyPGresult(PGconn *conn, ExecStatusType status)
 				/* non-error cases */
 				break;
 			default:
-				pqSetResultError(result, conn->errorMessage);
+				pqSetResultError(result, conn->errorMessage.data);
 				break;
 		}
 	}
@@ -298,6 +301,25 @@ pqSetResultError(PGresult *res, const char *msg)
 		res->errMsg = NULL;
 }
 
+/*
+ * pqCatenateResultError -
+ *		concatenate a new error message to the one already in a PGresult
+ */
+static void
+pqCatenateResultError(PGresult *res, const char *msg)
+{
+	PQExpBufferData  errorBuf;
+
+	if (!res || !msg)
+		return;
+	initPQExpBuffer(&errorBuf);
+	if (res->errMsg)
+		appendPQExpBufferStr(&errorBuf, res->errMsg);
+	appendPQExpBufferStr(&errorBuf, msg);
+	pqSetResultError(res, errorBuf.data);
+	termPQExpBuffer(&errorBuf);
+}
+
 /*
  * PQclear -
  *	  free's the memory associated with a PGresult
@@ -338,6 +360,72 @@ pqClearAsyncResult(PGconn *conn)
 	conn->curTuple = NULL;
 }
 
+/*
+ * This subroutine deletes any existing async result, sets conn->result
+ * to a PGresult with status PGRES_FATAL_ERROR, and stores the current
+ * contents of conn->errorMessage into that result.  It differs from a
+ * plain call on PQmakeEmptyPGresult() in that if there is already an
+ * async result with status PGRES_FATAL_ERROR, the current error message
+ * is APPENDED to the old error message instead of replacing it.  This
+ * behavior lets us report multiple error conditions properly, if necessary.
+ * (An example where this is needed is when the backend sends an 'E' message
+ * and immediately closes the connection --- we want to report both the
+ * backend error and the connection closure error.)
+ */
+static void
+saveErrorResult(PGconn *conn)
+{
+	/* If no old async result, just let PQmakeEmptyPGresult make one.
+	 * Likewise if old result is not an error message.
+	 */
+	if (conn->result == NULL ||
+		conn->result->resultStatus != PGRES_FATAL_ERROR ||
+		conn->result->errMsg == NULL)
+	{
+		pqClearAsyncResult(conn);
+		conn->result = PQmakeEmptyPGresult(conn, PGRES_FATAL_ERROR);
+	}
+	else
+	{
+		/* Else, concatenate error message to existing async result. */
+		pqCatenateResultError(conn->result, conn->errorMessage.data);
+	}
+}
+
+/*
+ * This subroutine prepares an async result object for return to the caller.
+ * If there is not already an async result object, build an error object
+ * using whatever is in conn->errorMessage.  In any case, clear the async
+ * result storage and make sure PQerrorMessage will agree with the result's
+ * error string.
+ */
+static PGresult *
+prepareAsyncResult(PGconn *conn)
+{
+	PGresult   *res;
+
+	/*
+	 * 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->curTuple = NULL;		/* just in case */
+	if (!res)
+		res = PQmakeEmptyPGresult(conn, PGRES_FATAL_ERROR);
+	else
+	{
+		/*
+		 * Make sure PQerrorMessage agrees with result; it could
+		 * be different if we have concatenated messages.
+		 */
+		resetPQExpBuffer(&conn->errorMessage);
+		appendPQExpBufferStr(&conn->errorMessage,
+							 PQresultErrorMessage(res));
+	}
+	return res;
+}
 
 /*
  * addTuple
@@ -394,37 +482,33 @@ PQsendQuery(PGconn *conn, const char *query)
 {
 	if (!conn)
 		return 0;
+
+	/* clear the error string */
+	resetPQExpBuffer(&conn->errorMessage);
+
 	if (!query)
 	{
-		sprintf(conn->errorMessage, "PQsendQuery() -- query pointer is null.");
-		return 0;
-	}
-	/* check to see if the query string is too long */
-	if (strlen(query) > MAX_MESSAGE_LEN - 2)
-	{
-		sprintf(conn->errorMessage, "PQsendQuery() -- query is too long.  "
-				"Maximum length is %d\n", MAX_MESSAGE_LEN - 2);
+		printfPQExpBuffer(&conn->errorMessage,
+						  "PQsendQuery() -- query pointer is null.\n");
 		return 0;
 	}
 
 	/* Don't try to send if we know there's no live connection. */
 	if (conn->status != CONNECTION_OK)
 	{
-		sprintf(conn->errorMessage, "PQsendQuery() -- There is no connection "
-				"to the backend.\n");
+		printfPQExpBuffer(&conn->errorMessage,
+						  "PQsendQuery() -- There is no connection "
+						  "to the backend.\n");
 		return 0;
 	}
 	/* Can't send while already busy, either. */
 	if (conn->asyncStatus != PGASYNC_IDLE)
 	{
-		sprintf(conn->errorMessage,
-				"PQsendQuery() -- another query already in progress.");
+		printfPQExpBuffer(&conn->errorMessage,
+						  "PQsendQuery() -- another query already in progress.\n");
 		return 0;
 	}
 
-	/* clear the error string */
-	conn->errorMessage[0] = '\0';
-
 	/* initialize async result-accumulation state */
 	conn->result = NULL;
 	conn->curTuple = NULL;
@@ -456,9 +540,6 @@ PQsendQuery(PGconn *conn, const char *query)
 static void
 handleSendFailure(PGconn *conn)
 {
-	/* Preserve the error message emitted by the failing output routine */
-	char * svErrMsg = strdup(conn->errorMessage);
-
 	/*
 	 * Accept any available input data, ignoring errors.  Note that if
 	 * pqReadData decides the backend has closed the channel, it will
@@ -472,11 +553,6 @@ handleSendFailure(PGconn *conn)
 	 * state, only NOTICE and NOTIFY messages will be eaten.
 	 */
 	parseInput(conn);
-
-	/* Restore error message generated by output routine, if any. */
-	if (*svErrMsg != '\0')
-		strcpy(conn->errorMessage, svErrMsg);
-	free(svErrMsg);
 }
 
 /*
@@ -514,6 +590,7 @@ static void
 parseInput(PGconn *conn)
 {
 	char		id;
+	char		noticeWorkspace[128];
 
 	/*
 	 * Loop to parse successive complete messages available in the buffer.
@@ -565,10 +642,10 @@ parseInput(PGconn *conn)
 			{
 				if (conn->asyncStatus == PGASYNC_IDLE)
 				{
-					sprintf(conn->errorMessage,
-					  "Backend message type 0x%02x arrived while idle\n",
+					sprintf(noticeWorkspace,
+							"Backend message type 0x%02x arrived while idle\n",
 							id);
-					DONOTICE(conn, conn->errorMessage);
+					DONOTICE(conn, noticeWorkspace);
 					/* Discard the unexpected message; good idea?? */
 					conn->inStart = conn->inEnd;
 				}
@@ -577,21 +654,20 @@ parseInput(PGconn *conn)
 			switch (id)
 			{
 				case 'C':		/* command complete */
+					if (pqGets(&conn->workBuffer, conn))
+						return;
 					if (conn->result == NULL)
 						conn->result = PQmakeEmptyPGresult(conn,
-													   PGRES_COMMAND_OK);
-					if (pqGets(conn->result->cmdStatus, CMDSTATUS_LEN, conn))
-						return;
+														   PGRES_COMMAND_OK);
+					strncpy(conn->result->cmdStatus, conn->workBuffer.data,
+							CMDSTATUS_LEN);
 					conn->asyncStatus = PGASYNC_READY;
 					break;
 				case 'E':		/* error return */
-					if (pqGets(conn->errorMessage, ERROR_MSG_LENGTH, conn))
+					if (pqGets(& conn->errorMessage, conn))
 						return;
-					/* delete any partially constructed result */
-					pqClearAsyncResult(conn);
-					/* and build an error result holding the error message */
-					conn->result = PQmakeEmptyPGresult(conn,
-													   PGRES_FATAL_ERROR);
+					/* build an error result holding the error message */
+					saveErrorResult(conn);
 					conn->asyncStatus = PGASYNC_READY;
 					break;
 				case 'Z':		/* backend is ready for new query */
@@ -603,9 +679,10 @@ parseInput(PGconn *conn)
 						return;
 					if (id != '\0')
 					{
-						sprintf(conn->errorMessage,
-						  "unexpected character %c following 'I'\n", id);
-						DONOTICE(conn, conn->errorMessage);
+						sprintf(noticeWorkspace,
+								"unexpected character %c following 'I'\n",
+								id);
+						DONOTICE(conn, noticeWorkspace);
 					}
 					if (conn->result == NULL)
 						conn->result = PQmakeEmptyPGresult(conn,
@@ -625,7 +702,7 @@ parseInput(PGconn *conn)
 						return;
 					break;
 				case 'P':		/* synchronous (normal) portal */
-					if (pqGets(conn->errorMessage, ERROR_MSG_LENGTH, conn))
+					if (pqGets(&conn->workBuffer, conn))
 						return;
 					/* We pretty much ignore this message type... */
 					break;
@@ -660,9 +737,9 @@ parseInput(PGconn *conn)
 					}
 					else
 					{
-						sprintf(conn->errorMessage,
-							 "Backend sent D message without prior T\n");
-						DONOTICE(conn, conn->errorMessage);
+						sprintf(noticeWorkspace,
+								"Backend sent D message without prior T\n");
+						DONOTICE(conn, noticeWorkspace);
 						/* Discard the unexpected message; good idea?? */
 						conn->inStart = conn->inEnd;
 						return;
@@ -677,9 +754,9 @@ parseInput(PGconn *conn)
 					}
 					else
 					{
-						sprintf(conn->errorMessage,
-							 "Backend sent B message without prior T\n");
-						DONOTICE(conn, conn->errorMessage);
+						sprintf(noticeWorkspace,
+								"Backend sent B message without prior T\n");
+						DONOTICE(conn, noticeWorkspace);
 						/* Discard the unexpected message; good idea?? */
 						conn->inStart = conn->inEnd;
 						return;
@@ -692,18 +769,15 @@ parseInput(PGconn *conn)
 					conn->asyncStatus = PGASYNC_COPY_OUT;
 					break;
 				default:
-					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",
-							id);
+					printfPQExpBuffer(&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",
+						id);
+					/* build an error result holding the error message */
+					saveErrorResult(conn);
 					/* 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 */
@@ -753,12 +827,11 @@ getRowDescriptions(PGconn *conn)
 	/* get type info */
 	for (i = 0; i < nfields; i++)
 	{
-		char		typName[MAX_MESSAGE_LEN];
 		int			typid;
 		int			typlen;
 		int			atttypmod;
 
-		if (pqGets(typName, MAX_MESSAGE_LEN, conn) ||
+		if (pqGets(&conn->workBuffer, conn) ||
 			pqGetInt(&typid, 4, conn) ||
 			pqGetInt(&typlen, 2, conn) ||
 			pqGetInt(&atttypmod, 4, conn))
@@ -777,7 +850,8 @@ getRowDescriptions(PGconn *conn)
 		 */
 		if (typlen == 0xFFFF)
 			typlen = -1;
-		result->attDescs[i].name = pqResultStrdup(result, typName);
+		result->attDescs[i].name = pqResultStrdup(result,
+												  conn->workBuffer.data);
 		result->attDescs[i].typid = typid;
 		result->attDescs[i].typlen = typlen;
 		result->attDescs[i].atttypmod = atttypmod;
@@ -804,8 +878,9 @@ getAnotherTuple(PGconn *conn, int binary)
 	PGresult   *result = conn->result;
 	int			nfields = result->numAttributes;
 	PGresAttValue *tup;
-	char		bitmap[MAX_FIELDS];		/* the backend sends us a bitmap
-										 * of which attributes are null */
+	/* the backend sends us a bitmap of which attributes are null */
+	char		std_bitmap[64]; /* used unless it doesn't fit */
+	char	   *bitmap = std_bitmap;
 	int			i;
 	int			nbytes;			/* the number of bytes in bitmap  */
 	char		bmap;			/* One byte of the bitmap */
@@ -828,21 +903,12 @@ getAnotherTuple(PGconn *conn, int binary)
 
 	/* Get the null-value bitmap */
 	nbytes = (nfields + BYTELEN - 1) / BYTELEN;
-	if (nbytes >= MAX_FIELDS)
-	{
-		/* 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;
-		return EOF;
-	}
+	/* malloc() only for unusually large field counts... */
+	if (nbytes > sizeof(std_bitmap))
+		bitmap = (char *) malloc(nbytes);
 
 	if (pqGetnchar(bitmap, nbytes, conn))
-		return EOF;
+		goto EOFexit;
 
 	/* Scan the fields */
 	bitmap_index = 0;
@@ -861,7 +927,7 @@ getAnotherTuple(PGconn *conn, int binary)
 		{
 			/* get the value length (the first four bytes are for length) */
 			if (pqGetInt(&vlen, 4, conn))
-				return EOF;
+				goto EOFexit;
 			if (binary == 0)
 				vlen = vlen - 4;
 			if (vlen < 0)
@@ -876,7 +942,7 @@ getAnotherTuple(PGconn *conn, int binary)
 			/* read in the value */
 			if (vlen > 0)
 				if (pqGetnchar((char *) (tup[i].value), vlen, conn))
-					return EOF;
+					goto EOFexit;
 			/* we have to terminate this ourselves */
 			tup[i].value[vlen] = '\0';
 		}
@@ -897,17 +963,27 @@ getAnotherTuple(PGconn *conn, int binary)
 		goto outOfMemory;
 	/* and reset for a new message */
 	conn->curTuple = NULL;
+
+	if (bitmap != std_bitmap)
+		free(bitmap);
 	return 0;
 
 outOfMemory:
 	/* Replace partially constructed result with an error result */
+	/* we do NOT use saveErrorResult() here, because of the likelihood
+	 * that there's not enough memory to concatenate messages...
+	 */
 	pqClearAsyncResult(conn);
-	sprintf(conn->errorMessage,
-			"getAnotherTuple() -- out of memory for result\n");
+	printfPQExpBuffer(&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;
+
+EOFexit:
+	if (bitmap != std_bitmap)
+		free(bitmap);
 	return EOF;
 }
 
@@ -955,10 +1031,12 @@ PQgetResult(PGconn *conn)
 		if (pqWait(TRUE, FALSE, conn) ||
 			pqReadData(conn) < 0)
 		{
-			pqClearAsyncResult(conn);
+			/* conn->errorMessage has been set by pqWait or pqReadData.
+			 * We want to append it to any already-received error message.
+			 */
+			saveErrorResult(conn);
 			conn->asyncStatus = PGASYNC_IDLE;
-			/* conn->errorMessage has been set by pqWait or pqReadData. */
-			return PQmakeEmptyPGresult(conn, PGRES_FATAL_ERROR);
+			return prepareAsyncResult(conn);
 		}
 		/* Parse it. */
 		parseInput(conn);
@@ -971,28 +1049,7 @@ PQgetResult(PGconn *conn)
 			res = NULL;			/* query is complete */
 			break;
 		case PGASYNC_READY:
-
-			/*
-			 * 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->curTuple = NULL;		/* just in case */
-			if (!res)
-				res = PQmakeEmptyPGresult(conn, PGRES_FATAL_ERROR);
-			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));
-			}
+			res = prepareAsyncResult(conn);
 			/* Set the state back to BUSY, allowing parsing to proceed. */
 			conn->asyncStatus = PGASYNC_BUSY;
 			break;
@@ -1003,9 +1060,9 @@ PQgetResult(PGconn *conn)
 			res = PQmakeEmptyPGresult(conn, PGRES_COPY_OUT);
 			break;
 		default:
-			sprintf(conn->errorMessage,
-					"PQgetResult: Unexpected asyncStatus %d\n",
-					(int) conn->asyncStatus);
+			printfPQExpBuffer(&conn->errorMessage,
+							  "PQgetResult: Unexpected asyncStatus %d\n",
+							  (int) conn->asyncStatus);
 			res = PQmakeEmptyPGresult(conn, PGRES_FATAL_ERROR);
 			break;
 	}
@@ -1043,7 +1100,7 @@ PQexec(PGconn *conn, const char *query)
 			result->resultStatus == PGRES_COPY_OUT)
 		{
 			PQclear(result);
-			sprintf(conn->errorMessage,
+			printfPQExpBuffer(&conn->errorMessage,
 				"PQexec: you gotta get out of a COPY state yourself.\n");
 			return NULL;
 		}
@@ -1056,14 +1113,30 @@ PQexec(PGconn *conn, const char *query)
 
 	/*
 	 * For backwards compatibility, return the last result if there are
-	 * more than one.  We have to stop if we see copy in/out, however. We
-	 * will resume parsing when application calls PQendcopy.
+	 * more than one --- but merge error messages if we get more than one
+	 * error result.
+	 *
+	 * We have to stop if we see copy in/out, however.
+	 * We will resume parsing when application calls PQendcopy.
 	 */
 	lastResult = NULL;
 	while ((result = PQgetResult(conn)) != NULL)
 	{
 		if (lastResult)
-			PQclear(lastResult);
+		{
+			if (lastResult->resultStatus == PGRES_FATAL_ERROR &&
+				result->resultStatus == PGRES_FATAL_ERROR)
+			{
+				pqCatenateResultError(lastResult, result->errMsg);
+				PQclear(result);
+				result = lastResult;
+				/* Make sure PQerrorMessage agrees with catenated result */
+				resetPQExpBuffer(&conn->errorMessage);
+				appendPQExpBufferStr(&conn->errorMessage, result->errMsg);
+			}
+			else
+				PQclear(lastResult);
+		}
 		lastResult = result;
 		if (result->resultStatus == PGRES_COPY_IN ||
 			result->resultStatus == PGRES_COPY_OUT)
@@ -1083,9 +1156,20 @@ PQexec(PGconn *conn, const char *query)
 static int
 getNotice(PGconn *conn)
 {
-	if (pqGets(conn->errorMessage, ERROR_MSG_LENGTH, conn))
+	/* Since the Notice might be pretty long, we create a temporary
+	 * PQExpBuffer rather than using conn->workBuffer.  workBuffer is
+	 * intended for stuff that is expected to be short.
+	 */
+	PQExpBufferData  noticeBuf;
+
+	initPQExpBuffer(&noticeBuf);
+	if (pqGets(&noticeBuf, conn))
+	{
+		termPQExpBuffer(&noticeBuf);
 		return EOF;
-	DONOTICE(conn, conn->errorMessage);
+	}
+	DONOTICE(conn, noticeBuf.data);
+	termPQExpBuffer(&noticeBuf);
 	return 0;
 }
 
@@ -1099,15 +1183,16 @@ getNotice(PGconn *conn)
 static int
 getNotify(PGconn *conn)
 {
-	PGnotify	tempNotify;
+	int			be_pid;
 	PGnotify   *newNotify;
 
-	if (pqGetInt(&(tempNotify.be_pid), 4, conn))
+	if (pqGetInt(&be_pid, 4, conn))
 		return EOF;
-	if (pqGets(tempNotify.relname, NAMEDATALEN, conn))
+	if (pqGets(&conn->workBuffer, conn))
 		return EOF;
 	newNotify = (PGnotify *) malloc(sizeof(PGnotify));
-	memcpy(newNotify, &tempNotify, sizeof(PGnotify));
+	strncpy(newNotify->relname, conn->workBuffer.data, NAMEDATALEN);
+	newNotify->be_pid = be_pid;
 	DLAddTail(conn->notifyList, DLNewElem(newNotify));
 	return 0;
 }
@@ -1342,8 +1427,8 @@ PQendcopy(PGconn *conn)
 	if (conn->asyncStatus != PGASYNC_COPY_IN &&
 		conn->asyncStatus != PGASYNC_COPY_OUT)
 	{
-		sprintf(conn->errorMessage,
-			 "PQendcopy() -- I don't think there's a copy in progress.");
+		printfPQExpBuffer(&conn->errorMessage,
+			 "PQendcopy() -- I don't think there's a copy in progress.\n");
 		return 1;
 	}
 
@@ -1351,7 +1436,7 @@ PQendcopy(PGconn *conn)
 
 	/* Return to active duty */
 	conn->asyncStatus = PGASYNC_BUSY;
-	conn->errorMessage[0] = '\0';
+	resetPQExpBuffer(&conn->errorMessage);
 
 	/* Wait for the completion response */
 	result = PQgetResult(conn);
@@ -1370,8 +1455,8 @@ PQendcopy(PGconn *conn)
 	 */
 	PQclear(result);
 
-	if (conn->errorMessage[0])
-		DONOTICE(conn, conn->errorMessage);
+	if (conn->errorMessage.len > 0)
+		DONOTICE(conn, conn->errorMessage.data);
 
 	DONOTICE(conn, "PQendcopy: resetting connection\n");
 
@@ -1423,15 +1508,17 @@ PQfn(PGconn *conn,
 	if (!conn)
 		return NULL;
 
-	if (conn->sock < 0 || conn->asyncStatus != PGASYNC_IDLE)
+	/* clear the error string */
+	resetPQExpBuffer(&conn->errorMessage);
+
+	if (conn->sock < 0 || conn->asyncStatus != PGASYNC_IDLE ||
+		conn->result != NULL)
 	{
-		sprintf(conn->errorMessage, "PQfn() -- connection in wrong state\n");
+		printfPQExpBuffer(&conn->errorMessage,
+						  "PQfn() -- connection in wrong state\n");
 		return NULL;
 	}
 
-	/* clear the error string */
-	conn->errorMessage[0] = '\0';
-
 	if (pqPuts("F ", conn) ||			/* function */
 		pqPutInt(fnid, 4, conn) ||		/* function id */
 		pqPutInt(nargs, 4, conn))		/* # of args */
@@ -1529,15 +1616,19 @@ PQfn(PGconn *conn,
 				else
 				{
 					/* The backend violates the protocol. */
-					sprintf(conn->errorMessage,
-							"FATAL: PQfn: protocol error: id=%x\n", id);
+					printfPQExpBuffer(&conn->errorMessage,
+									  "FATAL: PQfn: protocol error: id=0x%x\n",
+									  id);
+					saveErrorResult(conn);
 					conn->inStart = conn->inCursor;
-					return PQmakeEmptyPGresult(conn, PGRES_FATAL_ERROR);
+					return prepareAsyncResult(conn);
 				}
 				break;
 			case 'E':			/* error return */
-				if (pqGets(conn->errorMessage, ERROR_MSG_LENGTH, conn))
+				if (pqGets(&conn->errorMessage, conn))
 					continue;
+				/* build an error result holding the error message */
+				saveErrorResult(conn);
 				status = PGRES_FATAL_ERROR;
 				break;
 			case 'A':			/* notify message */
@@ -1553,21 +1644,30 @@ PQfn(PGconn *conn,
 			case 'Z':			/* backend is ready for new query */
 				/* consume the message and exit */
 				conn->inStart = conn->inCursor;
+				/* if we saved a result object (probably an error), use it */
+				if (conn->result)
+					return prepareAsyncResult(conn);
 				return PQmakeEmptyPGresult(conn, status);
 			default:
 				/* The backend violates the protocol. */
-				sprintf(conn->errorMessage,
-						"FATAL: PQfn: protocol error: id=%x\n", id);
+				printfPQExpBuffer(&conn->errorMessage,
+								  "FATAL: PQfn: protocol error: id=0x%x\n",
+								  id);
+				saveErrorResult(conn);
 				conn->inStart = conn->inCursor;
-				return PQmakeEmptyPGresult(conn, PGRES_FATAL_ERROR);
+				return prepareAsyncResult(conn);
 		}
 		/* Completed this message, keep going */
 		conn->inStart = conn->inCursor;
 		needInput = false;
 	}
 
-	/* we fall out of the loop only upon failing to read data */
-	return PQmakeEmptyPGresult(conn, PGRES_FATAL_ERROR);
+	/* We fall out of the loop only upon failing to read data.
+	 * conn->errorMessage has been set by pqWait or pqReadData.
+	 * We want to append it to any already-received error message.
+	 */
+	saveErrorResult(conn);
+	return prepareAsyncResult(conn);
 }
 
 
@@ -1630,16 +1730,18 @@ PQbinaryTuples(PGresult *res)
 static int
 check_field_number(const char *routineName, PGresult *res, int field_num)
 {
+	char noticeBuf[128];
+
 	if (!res)
 		return FALSE;			/* no way to display error message... */
 	if (field_num < 0 || field_num >= res->numAttributes)
 	{
 		if (res->conn)
 		{
-			sprintf(res->conn->errorMessage,
+			sprintf(noticeBuf,
 					"%s: ERROR! field number %d is out of range 0..%d\n",
 					routineName, field_num, res->numAttributes - 1);
-			DONOTICE(res->conn, res->conn->errorMessage);
+			DONOTICE(res->conn, noticeBuf);
 		}
 		return FALSE;
 	}
@@ -1650,16 +1752,18 @@ static int
 check_tuple_field_number(const char *routineName, PGresult *res,
 						 int tup_num, int field_num)
 {
+	char noticeBuf[128];
+
 	if (!res)
 		return FALSE;			/* no way to display error message... */
 	if (tup_num < 0 || tup_num >= res->ntups)
 	{
 		if (res->conn)
 		{
-			sprintf(res->conn->errorMessage,
+			sprintf(noticeBuf,
 					"%s: ERROR! tuple number %d is out of range 0..%d\n",
 					routineName, tup_num, res->ntups - 1);
-			DONOTICE(res->conn, res->conn->errorMessage);
+			DONOTICE(res->conn, noticeBuf);
 		}
 		return FALSE;
 	}
@@ -1667,10 +1771,10 @@ check_tuple_field_number(const char *routineName, PGresult *res,
 	{
 		if (res->conn)
 		{
-			sprintf(res->conn->errorMessage,
+			sprintf(noticeBuf,
 					"%s: ERROR! field number %d is out of range 0..%d\n",
 					routineName, field_num, res->numAttributes - 1);
-			DONOTICE(res->conn, res->conn->errorMessage);
+			DONOTICE(res->conn, noticeBuf);
 		}
 		return FALSE;
 	}
@@ -1830,6 +1934,8 @@ PQoidStatus(PGresult *res)
 const char *
 PQcmdTuples(PGresult *res)
 {
+	char noticeBuf[128];
+
 	if (!res)
 		return "";
 
@@ -1843,10 +1949,10 @@ PQcmdTuples(PGresult *res)
 		{
 			if (res->conn)
 			{
-				sprintf(res->conn->errorMessage,
+				sprintf(noticeBuf,
 						"PQcmdTuples (%s) -- bad input from server\n",
 						res->cmdStatus);
-				DONOTICE(res->conn, res->conn->errorMessage);
+				DONOTICE(res->conn, noticeBuf);
 			}
 			return "";
 		}
@@ -1859,9 +1965,9 @@ PQcmdTuples(PGresult *res)
 		{
 			if (res->conn)
 			{
-				sprintf(res->conn->errorMessage,
+				sprintf(noticeBuf,
 					 "PQcmdTuples (INSERT) -- there's no # of tuples\n");
-				DONOTICE(res->conn, res->conn->errorMessage);
+				DONOTICE(res->conn, noticeBuf);
 			}
 			return "";
 		}
diff --git a/src/interfaces/libpq/fe-lobj.c b/src/interfaces/libpq/fe-lobj.c
index a105d0344b0..8793e24cf89 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.24 1999/07/19 06:25:39 momjian Exp $
+ *	  $Header: /cvsroot/pgsql/src/interfaces/libpq/fe-lobj.c,v 1.25 1999/08/31 01:37:36 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -397,8 +397,9 @@ lo_import(PGconn *conn, char *filename)
 #endif
 	if (fd < 0)
 	{							/* error */
-		sprintf(conn->errorMessage,
-				"lo_import: can't open unix file\"%s\"\n", filename);
+		printfPQExpBuffer(&conn->errorMessage,
+						  "lo_import: can't open unix file\"%s\"\n",
+						  filename);
 		return InvalidOid;
 	}
 
@@ -408,16 +409,18 @@ lo_import(PGconn *conn, char *filename)
 	lobjOid = lo_creat(conn, INV_READ | INV_WRITE);
 	if (lobjOid == InvalidOid)
 	{
-		sprintf(conn->errorMessage,
-			  "lo_import: can't create inv object for \"%s\"", filename);
+		printfPQExpBuffer(&conn->errorMessage,
+						  "lo_import: can't create inv object for \"%s\"",
+						  filename);
 		return InvalidOid;
 	}
 
 	lobj = lo_open(conn, lobjOid, INV_WRITE);
 	if (lobj == -1)
 	{
-		sprintf(conn->errorMessage,
-				"lo_import: could not open inv object oid %u", lobjOid);
+		printfPQExpBuffer(&conn->errorMessage,
+						  "lo_import: could not open inv object oid %u",
+						  lobjOid);
 		return InvalidOid;
 	}
 
@@ -429,8 +432,9 @@ lo_import(PGconn *conn, char *filename)
 		tmp = lo_write(conn, lobj, buf, nbytes);
 		if (tmp < nbytes)
 		{
-			sprintf(conn->errorMessage,
-					"lo_import: error while reading \"%s\"", filename);
+			printfPQExpBuffer(&conn->errorMessage,
+							  "lo_import: error while reading \"%s\"",
+							  filename);
 			return InvalidOid;
 		}
 	}
@@ -461,8 +465,8 @@ lo_export(PGconn *conn, Oid lobjId, char *filename)
 	lobj = lo_open(conn, lobjId, INV_READ);
 	if (lobj == -1)
 	{
-		sprintf(conn->errorMessage,
-				"lo_export: can't open inv object %u", lobjId);
+		printfPQExpBuffer(&conn->errorMessage,
+						  "lo_export: can't open inv object %u", lobjId);
 		return -1;
 	}
 
@@ -476,8 +480,9 @@ lo_export(PGconn *conn, Oid lobjId, char *filename)
 #endif
 	if (fd < 0)
 	{							/* error */
-		sprintf(conn->errorMessage,
-				"lo_export: can't open unix file\"%s\"", filename);
+		printfPQExpBuffer(&conn->errorMessage,
+						  "lo_export: can't open unix file\"%s\"",
+						  filename);
 		return 0;
 	}
 
@@ -489,9 +494,9 @@ lo_export(PGconn *conn, Oid lobjId, char *filename)
 		tmp = write(fd, buf, nbytes);
 		if (tmp < nbytes)
 		{
-			sprintf(conn->errorMessage,
-					"lo_export: error while writing \"%s\"",
-					filename);
+			printfPQExpBuffer(&conn->errorMessage,
+							  "lo_export: error while writing \"%s\"",
+							  filename);
 			return -1;
 		}
 	}
@@ -527,8 +532,8 @@ lo_initialize(PGconn *conn)
 	lobjfuncs = (PGlobjfuncs *) malloc(sizeof(PGlobjfuncs));
 	if (lobjfuncs == (PGlobjfuncs *) NULL)
 	{
-		strcpy(conn->errorMessage,
-			   "FATAL: malloc() failed in lo_initialize()\n");
+		printfPQExpBuffer(&conn->errorMessage,
+						  "FATAL: malloc() failed in lo_initialize()\n");
 		return -1;
 	}
 	MemSet((char *) lobjfuncs, 0, sizeof(PGlobjfuncs));
@@ -556,8 +561,8 @@ lo_initialize(PGconn *conn)
 	{
 		free(lobjfuncs);
 		PQclear(res);
-		strcpy(conn->errorMessage,
-			   "ERROR: SELECT didn't return data in lo_initialize()\n");
+		printfPQExpBuffer(&conn->errorMessage,
+						  "ERROR: SELECT didn't return data in lo_initialize()\n");
 		return -1;
 	}
 
@@ -596,57 +601,57 @@ lo_initialize(PGconn *conn)
 	 */
 	if (lobjfuncs->fn_lo_open == 0)
 	{
-		strcpy(conn->errorMessage,
-			   "ERROR: Cannot determine OID for function lo_open\n");
+		printfPQExpBuffer(&conn->errorMessage,
+						  "ERROR: Cannot determine OID for function lo_open\n");
 		free(lobjfuncs);
 		return -1;
 	}
 	if (lobjfuncs->fn_lo_close == 0)
 	{
-		strcpy(conn->errorMessage,
-			   "ERROR: Cannot determine OID for function lo_close\n");
+		printfPQExpBuffer(&conn->errorMessage,
+						  "ERROR: Cannot determine OID for function lo_close\n");
 		free(lobjfuncs);
 		return -1;
 	}
 	if (lobjfuncs->fn_lo_creat == 0)
 	{
-		strcpy(conn->errorMessage,
-			   "ERROR: Cannot determine OID for function lo_creat\n");
+		printfPQExpBuffer(&conn->errorMessage,
+						  "ERROR: Cannot determine OID for function lo_creat\n");
 		free(lobjfuncs);
 		return -1;
 	}
 	if (lobjfuncs->fn_lo_unlink == 0)
 	{
-		strcpy(conn->errorMessage,
-			   "ERROR: Cannot determine OID for function lo_unlink\n");
+		printfPQExpBuffer(&conn->errorMessage,
+						  "ERROR: Cannot determine OID for function lo_unlink\n");
 		free(lobjfuncs);
 		return -1;
 	}
 	if (lobjfuncs->fn_lo_lseek == 0)
 	{
-		strcpy(conn->errorMessage,
-			   "ERROR: Cannot determine OID for function lo_lseek\n");
+		printfPQExpBuffer(&conn->errorMessage,
+						  "ERROR: Cannot determine OID for function lo_lseek\n");
 		free(lobjfuncs);
 		return -1;
 	}
 	if (lobjfuncs->fn_lo_tell == 0)
 	{
-		strcpy(conn->errorMessage,
-			   "ERROR: Cannot determine OID for function lo_tell\n");
+		printfPQExpBuffer(&conn->errorMessage,
+						  "ERROR: Cannot determine OID for function lo_tell\n");
 		free(lobjfuncs);
 		return -1;
 	}
 	if (lobjfuncs->fn_lo_read == 0)
 	{
-		strcpy(conn->errorMessage,
-			   "ERROR: Cannot determine OID for function loread\n");
+		printfPQExpBuffer(&conn->errorMessage,
+						  "ERROR: Cannot determine OID for function loread\n");
 		free(lobjfuncs);
 		return -1;
 	}
 	if (lobjfuncs->fn_lo_write == 0)
 	{
-		strcpy(conn->errorMessage,
-			   "ERROR: Cannot determine OID for function lowrite\n");
+		printfPQExpBuffer(&conn->errorMessage,
+						  "ERROR: Cannot determine OID for function lowrite\n");
 		free(lobjfuncs);
 		return -1;
 	}
diff --git a/src/interfaces/libpq/fe-misc.c b/src/interfaces/libpq/fe-misc.c
index 63558ec33c7..84149d12773 100644
--- a/src/interfaces/libpq/fe-misc.c
+++ b/src/interfaces/libpq/fe-misc.c
@@ -24,7 +24,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/interfaces/libpq/fe-misc.c,v 1.28 1999/07/19 06:25:40 momjian Exp $
+ *	  $Header: /cvsroot/pgsql/src/interfaces/libpq/fe-misc.c,v 1.29 1999/08/31 01:37:36 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -106,12 +106,12 @@ pqPutBytes(const char *s, int nbytes, PGconn *conn)
 /* --------------------------------------------------------------------- */
 /* pqGets:
    get a null-terminated string from the connection,
-   and store it in a buffer of size maxlen bytes.
-   If the incoming string is >= maxlen bytes, all of it is read,
+   and store it in an expansible PQExpBuffer.
+   If we run out of memory, all of the string is still read,
    but the excess characters are silently discarded.
 */
 int
-pqGets(char *s, int maxlen, PGconn *conn)
+pqGets(PQExpBuffer buf, PGconn *conn)
 {
 	/* Copy conn data to locals for faster search loop */
 	char	   *inBuffer = conn->inBuffer;
@@ -126,18 +126,15 @@ pqGets(char *s, int maxlen, PGconn *conn)
 		return EOF;
 
 	slen = inCursor - conn->inCursor;
-	if (slen < maxlen)
-		strcpy(s, inBuffer + conn->inCursor);
-	else
-	{
-		strncpy(s, inBuffer + conn->inCursor, maxlen - 1);
-		s[maxlen - 1] = '\0';
-	}
+
+	resetPQExpBuffer(buf);
+	appendBinaryPQExpBuffer(buf, inBuffer + conn->inCursor, slen);
 
 	conn->inCursor = ++inCursor;
 
 	if (conn->Pfdebug)
-		fprintf(conn->Pfdebug, "From backend> \"%s\"\n", s);
+		fprintf(conn->Pfdebug, "From backend> \"%s\"\n",
+				buf->data);
 
 	return 0;
 }
@@ -202,6 +199,7 @@ pqGetInt(int *result, int bytes, PGconn *conn)
 {
 	uint16		tmp2;
 	uint32		tmp4;
+	char		noticeBuf[64];
 
 	switch (bytes)
 	{
@@ -220,9 +218,9 @@ pqGetInt(int *result, int bytes, PGconn *conn)
 			*result = (int) ntohl(tmp4);
 			break;
 		default:
-			sprintf(conn->errorMessage,
+			sprintf(noticeBuf,
 					"pqGetInt: int size %d not supported\n", bytes);
-			DONOTICE(conn, conn->errorMessage);
+			DONOTICE(conn, noticeBuf);
 			return EOF;
 	}
 
@@ -242,6 +240,7 @@ pqPutInt(int value, int bytes, PGconn *conn)
 {
 	uint16		tmp2;
 	uint32		tmp4;
+	char		noticeBuf[64];
 
 	switch (bytes)
 	{
@@ -256,9 +255,9 @@ pqPutInt(int value, int bytes, PGconn *conn)
 				return EOF;
 			break;
 		default:
-			sprintf(conn->errorMessage,
+			sprintf(noticeBuf,
 					"pqPutInt: int size %d not supported\n", bytes);
-			DONOTICE(conn, conn->errorMessage);
+			DONOTICE(conn, noticeBuf);
 			return EOF;
 	}
 
@@ -287,9 +286,9 @@ pqReadReady(PGconn *conn)
 	if (select(conn->sock + 1, &input_mask, (fd_set *) NULL, (fd_set *) NULL,
 			   &timeout) < 0)
 	{
-		sprintf(conn->errorMessage,
-				"pqReadReady() -- select() failed: errno=%d\n%s\n",
-				errno, strerror(errno));
+		printfPQExpBuffer(&conn->errorMessage,
+						  "pqReadReady() -- select() failed: errno=%d\n%s\n",
+						  errno, strerror(errno));
 		return 0;
 	}
 	return FD_ISSET(conn->sock, &input_mask);
@@ -312,7 +311,8 @@ pqReadData(PGconn *conn)
 
 	if (conn->sock < 0)
 	{
-		strcpy(conn->errorMessage, "pqReadData() -- connection not open\n");
+		printfPQExpBuffer(&conn->errorMessage,
+						  "pqReadData() -- connection not open\n");
 		return -1;
 	}
 
@@ -333,9 +333,10 @@ pqReadData(PGconn *conn)
 	 * enlarge the buffer in case a single message exceeds the initial
 	 * buffer size.  We enlarge before filling the buffer entirely so as
 	 * to avoid asking the kernel for a partial packet. The magic constant
-	 * here should be at least one TCP packet.
+	 * here should be large enough for a TCP packet or Unix pipe
+	 * bufferload.  8K is the usual pipe buffer size, so...
 	 */
-	if (conn->inBufSize - conn->inEnd < 2000)
+	if (conn->inBufSize - conn->inEnd < 8192)
 	{
 		int			newSize = conn->inBufSize * 2;
 		char	   *newBuf = (char *) realloc(conn->inBuffer, newSize);
@@ -369,9 +370,9 @@ tryAgain:
 		if (errno == ECONNRESET)
 			goto definitelyFailed;
 #endif
-		sprintf(conn->errorMessage,
-				"pqReadData() --  read() failed: errno=%d\n%s\n",
-				errno, strerror(errno));
+		printfPQExpBuffer(&conn->errorMessage,
+						  "pqReadData() --  read() failed: errno=%d\n%s\n",
+						  errno, strerror(errno));
 		return -1;
 	}
 	if (nread > 0)
@@ -417,9 +418,9 @@ tryAgain2:
 		if (errno == ECONNRESET)
 			goto definitelyFailed;
 #endif
-		sprintf(conn->errorMessage,
-				"pqReadData() --  read() failed: errno=%d\n%s\n",
-				errno, strerror(errno));
+		printfPQExpBuffer(&conn->errorMessage,
+						  "pqReadData() --  read() failed: errno=%d\n%s\n",
+						  errno, strerror(errno));
 		return -1;
 	}
 	if (nread > 0)
@@ -433,7 +434,7 @@ tryAgain2:
 	 * This means the connection has been closed.  Cope.
 	 */
 definitelyFailed:
-	sprintf(conn->errorMessage,
+	printfPQExpBuffer(&conn->errorMessage,
 			"pqReadData() -- backend closed the channel unexpectedly.\n"
 			"\tThis probably means the backend terminated abnormally\n"
 			"\tbefore or while processing the request.\n");
@@ -459,7 +460,8 @@ pqFlush(PGconn *conn)
 
 	if (conn->sock < 0)
 	{
-		strcpy(conn->errorMessage, "pqFlush() -- connection not open\n");
+		printfPQExpBuffer(&conn->errorMessage,
+						  "pqFlush() -- connection not open\n");
 		return EOF;
 	}
 
@@ -499,7 +501,7 @@ pqFlush(PGconn *conn)
 #ifdef ECONNRESET
 				case ECONNRESET:
 #endif
-					sprintf(conn->errorMessage,
+					printfPQExpBuffer(&conn->errorMessage,
 							"pqFlush() -- backend closed the channel unexpectedly.\n"
 							"\tThis probably means the backend terminated abnormally"
 							" before or while processing the request.\n");
@@ -513,8 +515,8 @@ pqFlush(PGconn *conn)
 					return EOF;
 
 				default:
-					sprintf(conn->errorMessage,
-					  "pqFlush() --  couldn't send data: errno=%d\n%s\n",
+					printfPQExpBuffer(&conn->errorMessage,
+							"pqFlush() --  couldn't send data: errno=%d\n%s\n",
 							errno, strerror(errno));
 					/* We don't assume it's a fatal error... */
 					return EOF;
@@ -552,7 +554,8 @@ pqWait(int forRead, int forWrite, PGconn *conn)
 
 	if (conn->sock < 0)
 	{
-		strcpy(conn->errorMessage, "pqWait() -- connection not open\n");
+		printfPQExpBuffer(&conn->errorMessage,
+						  "pqWait() -- connection not open\n");
 		return EOF;
 	}
 
@@ -570,9 +573,9 @@ pqWait(int forRead, int forWrite, PGconn *conn)
 		{
 			if (errno == EINTR)
 				continue;
-			sprintf(conn->errorMessage,
-					"pqWait() -- select() failed: errno=%d\n%s\n",
-					errno, strerror(errno));
+			printfPQExpBuffer(&conn->errorMessage,
+							  "pqWait() -- select() failed: errno=%d\n%s\n",
+							  errno, strerror(errno));
 			return EOF;
 		}
 		/* On nonerror return, assume we're done */
diff --git a/src/interfaces/libpq/fe-print.c b/src/interfaces/libpq/fe-print.c
index 9151463fece..02f3455dcbc 100644
--- a/src/interfaces/libpq/fe-print.c
+++ b/src/interfaces/libpq/fe-print.c
@@ -9,7 +9,7 @@
  * didn't really belong there.
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/interfaces/libpq/fe-print.c,v 1.26 1999/07/19 06:25:40 momjian Exp $
+ *	  $Header: /cvsroot/pgsql/src/interfaces/libpq/fe-print.c,v 1.27 1999/08/31 01:37:37 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -51,7 +51,7 @@ static struct winsize
 
 
 static void do_field(PQprintOpt *po, PGresult *res,
-		 const int i, const int j, char *buf, const int fs_len,
+		 const int i, const int j, const int fs_len,
 		 char **fields,
 		 const int nFields, char **fieldNames,
 		 unsigned char *fieldNotNum, int *fieldMax,
@@ -103,7 +103,6 @@ PQprint(FILE *fout,
 		int			usePipe = 0;
 		pqsigfunc	oldsigpipehandler = NULL;
 		char	   *pagerenv;
-		char		buf[MAX_QUERY_SIZE + 1];
 
 		nTups = PQntuples(res);
 		if (!(fieldNames = (char **) calloc(nFields, sizeof(char *))))
@@ -254,7 +253,7 @@ PQprint(FILE *fout,
 					fprintf(fout, "-- RECORD %d --\n", i);
 			}
 			for (j = 0; j < nFields; j++)
-				do_field(po, res, i, j, buf, fs_len, fields, nFields,
+				do_field(po, res, i, j, fs_len, fields, nFields,
 						 fieldNames, fieldNotNum,
 						 fieldMax, fieldMaxLen, fout);
 			if (po->html3 && po->expanded)
@@ -332,7 +331,7 @@ PQdisplayTuples(PGresult *res,
 				j;
 	int			nFields;
 	int			nTuples;
-	int			fLength[MAX_FIELDS];
+	int		   *fLength = NULL;
 
 	if (fieldSep == NULL)
 		fieldSep = DEFAULT_FIELD_SEP;
@@ -344,19 +343,19 @@ PQdisplayTuples(PGresult *res,
 	if (fp == NULL)
 		fp = stdout;
 
-	/* Zero the initial field lengths */
-	for (j = 0; j < nFields; j++)
-		fLength[j] = strlen(PQfname(res, j));
-	/* Find the max length of each field in the result */
+	/* Figure the field lengths to align to */
 	/* will be somewhat time consuming for very large results */
 	if (fillAlign)
 	{
-		for (i = 0; i < nTuples; i++)
+		fLength = (int *) malloc(nFields * sizeof(int));
+		for (j = 0; j < nFields; j++)
 		{
-			for (j = 0; j < nFields; j++)
+			fLength[j] = strlen(PQfname(res, j));
+			for (i = 0; i < nTuples; i++)
 			{
-				if (PQgetlength(res, i, j) > fLength[j])
-					fLength[j] = PQgetlength(res, i, j);
+				int flen = PQgetlength(res, i, j);
+				if (flen > fLength[j])
+					fLength[j] = flen;
 			}
 		}
 	}
@@ -401,6 +400,9 @@ PQdisplayTuples(PGresult *res,
 				(PQntuples(res) == 1) ? "" : "s");
 
 	fflush(fp);
+
+	if (fLength)
+		free(fLength);
 }
 
 
@@ -522,7 +524,7 @@ PQmblen(unsigned char *s)
 
 static void
 do_field(PQprintOpt *po, PGresult *res,
-		 const int i, const int j, char *buf, const int fs_len,
+		 const int i, const int j, const int fs_len,
 		 char **fields,
 		 const int nFields, char **fieldNames,
 		 unsigned char *fieldNotNum, int *fieldMax,
@@ -530,8 +532,7 @@ do_field(PQprintOpt *po, PGresult *res,
 {
 
 	char	   *pval,
-			   *p,
-			   *o;
+			   *p;
 	int			plen;
 	bool		skipit;
 
@@ -553,62 +554,49 @@ do_field(PQprintOpt *po, PGresult *res,
 
 	if (!skipit)
 	{
-		char		ch = 0;
+		if (po->align && ! fieldNotNum[j])
+		{
+			/* Detect whether field contains non-numeric data */
+			char		ch = '0';
 
 #ifdef MULTIBYTE
-		int			len;
-
-		for (p = pval, o = buf; *p;
-			 len = PQmblen(p), memcpy(o, p, len),
-			 o += len, p += len)
+			for (p = pval; *p; p += PQmblen(p))
 #else
-		for (p = pval, o = buf; *p; *(o++) = *(p++))
+			for (p = pval; *p; p++)
 #endif
-		{
-			ch = *p;
-
+			{
+				ch = *p;
+				if (! ((ch >= '0' && ch <= '9') ||
+					   ch == '.' ||
+					   ch == 'E' ||
+					   ch == 'e' ||
+					   ch == ' ' ||
+					   ch == '-'))
+				{
+					fieldNotNum[j] = 1;
+					break;
+				}
+			}
 			/*
-			 * Consensus on pgsql-interfaces (as of Aug 1998) seems to be
-			 * that the print functions ought not insert backslashes.  If
-			 * you like them, you can re-enable this next bit.
+			 * Above loop will believe E in first column is numeric; also, we
+			 * insist on a digit in the last column for a numeric.	This test
+			 * is still not bulletproof but it handles most cases.
 			 */
-#ifdef GRATUITOUS_BACKSLASHES
-			if ((fs_len == 1 && (ch == *(po->fieldSep))) ||
-				ch == '\\' || ch == '\n')
-				*(o++) = '\\';
-#endif
-			if (po->align &&
-				!((ch >= '0' && ch <= '9') ||
-				  ch == '.' ||
-				  ch == 'E' ||
-				  ch == 'e' ||
-				  ch == ' ' ||
-				  ch == '-'))
+			if (*pval == 'E' || *pval == 'e' ||
+				!(ch >= '0' && ch <= '9'))
 				fieldNotNum[j] = 1;
 		}
-		*o = '\0';
-
-		/*
-		 * Above loop will believe E in first column is numeric; also, we
-		 * insist on a digit in the last column for a numeric.	This test
-		 * is still not bulletproof but it handles most cases.
-		 */
-		if (po->align &&
-			(*pval == 'E' || *pval == 'e' ||
-			 !(ch >= '0' && ch <= '9')))
-			fieldNotNum[j] = 1;
+
 		if (!po->expanded && (po->align || po->html3))
 		{
-			int			n = strlen(buf);
-
-			if (n > fieldMax[j])
-				fieldMax[j] = n;
-			if (!(fields[i * nFields + j] = (char *) malloc(n + 1)))
+			if (plen > fieldMax[j])
+				fieldMax[j] = plen;
+			if (!(fields[i * nFields + j] = (char *) malloc(plen + 1)))
 			{
 				perror("malloc");
 				exit(1);
 			}
-			strcpy(fields[i * nFields + j], buf);
+			strcpy(fields[i * nFields + j], pval);
 		}
 		else
 		{
@@ -620,23 +608,26 @@ do_field(PQprintOpt *po, PGresult *res,
 							"<td align=%s>%s</td></tr>\n",
 							fieldNames[j],
 							fieldNotNum[j] ? "left" : "right",
-							buf);
+							pval);
 				else
 				{
 					if (po->align)
 						fprintf(fout,
 								"%-*s%s %s\n",
-						fieldMaxLen - fs_len, fieldNames[j], po->fieldSep,
-								buf);
+								fieldMaxLen - fs_len, fieldNames[j],
+								po->fieldSep,
+								pval);
 					else
-						fprintf(fout, "%s%s%s\n", fieldNames[j], po->fieldSep, buf);
+						fprintf(fout,
+								"%s%s%s\n",
+								fieldNames[j], po->fieldSep, pval);
 				}
 			}
 			else
 			{
 				if (!po->html3)
 				{
-					fputs(buf, fout);
+					fputs(pval, fout);
 			efield:
 					if ((j + 1) < nFields)
 						fputs(po->fieldSep, fout);
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index f3b788dd930..d22d403e016 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.10 1999/07/13 20:00:37 momjian Exp $
+ * $Id: libpq-int.h,v 1.11 1999/08/31 01:37:37 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -21,12 +21,12 @@
 
 /* We assume libpq-fe.h has already been included. */
 
-/* ----------------
- *		include stuff common to fe and be
- * ----------------
- */
+/* include stuff common to fe and be */
 #include "libpq/pqcomm.h"
 #include "lib/dllist.h"
+/* include stuff found in fe only */
+#include "pqexpbuffer.h"
+
 
 /* libpq supports this version of the frontend/backend protocol.
  *
@@ -45,8 +45,6 @@
  * POSTGRES backend dependent Constants.
  */
 
-/* ERROR_MSG_LENGTH should really be the same as ELOG_MAXLEN in utils/elog.h*/
-#define ERROR_MSG_LENGTH 4096
 #define CMDSTATUS_LEN 40
 
 /*
@@ -115,7 +113,7 @@ struct pg_result
 	int			tupArrSize;		/* size of tuples array allocated */
 	ExecStatusType resultStatus;
 	char		cmdStatus[CMDSTATUS_LEN];		/* cmd status from the
-												 * last insert query */
+												 * last query */
 	int			binary;			/* binary tuple values if binary == 1,
 								 * otherwise ASCII */
 	PGconn	   *conn;			/* connection we did the query on, if any */
@@ -217,8 +215,11 @@ struct pg_conn
 	PGresult   *result;			/* result being constructed */
 	PGresAttValue *curTuple;	/* tuple currently being read */
 
-	/* Message space.  Placed last for code-size reasons. */
-	char		errorMessage[ERROR_MSG_LENGTH];
+	/* Buffer for current error message */
+	PQExpBufferData	errorMessage;	/* expansible string */
+
+	/* Buffer for receiving various parts of messages */
+	PQExpBufferData	workBuffer;	/* expansible string */
 };
 
 /* ----------------
@@ -249,7 +250,7 @@ extern void pqClearAsyncResult(PGconn *conn);
   * necessarily any error.
   */
 extern int	pqGetc(char *result, PGconn *conn);
-extern int	pqGets(char *s, int maxlen, PGconn *conn);
+extern int	pqGets(PQExpBuffer buf, PGconn *conn);
 extern int	pqPuts(const char *s, PGconn *conn);
 extern int	pqGetnchar(char *s, int len, PGconn *conn);
 extern int	pqPutnchar(const char *s, int len, PGconn *conn);
@@ -259,12 +260,6 @@ extern int	pqReadData(PGconn *conn);
 extern int	pqFlush(PGconn *conn);
 extern int	pqWait(int forRead, int forWrite, PGconn *conn);
 
-/* max length of message to send  */
-#define MAX_MESSAGE_LEN MAX_QUERY_SIZE
-
-/* maximum number of fields in a tuple */
-#define MAX_FIELDS 512
-
 /* bits in a byte */
 #define BYTELEN 8
 
diff --git a/src/interfaces/libpq/pqexpbuffer.c b/src/interfaces/libpq/pqexpbuffer.c
new file mode 100644
index 00000000000..ddaca7e67b5
--- /dev/null
+++ b/src/interfaces/libpq/pqexpbuffer.c
@@ -0,0 +1,254 @@
+/*-------------------------------------------------------------------------
+ *
+ * pqexpbuffer.c
+ *
+ * PQExpBuffer provides an indefinitely-extensible string data type.
+ * It can be used to buffer either ordinary C strings (null-terminated text)
+ * or arbitrary binary data.  All storage is allocated with malloc().
+ *
+ * This module is essentially the same as the backend's StringInfo data type,
+ * but it is intended for use in frontend libpq and client applications.
+ * Thus, it does not rely on palloc(), elog(), nor vsnprintf().
+ *
+ * Copyright (c) 1994, Regents of the University of California
+ *
+ * $Header: /cvsroot/pgsql/src/interfaces/libpq/pqexpbuffer.c,v 1.1 1999/08/31 01:37:37 tgl Exp $
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+#include "pqexpbuffer.h"
+
+/*
+ * createPQExpBuffer
+ *
+ * Create an empty 'PQExpBufferData' & return a pointer to it.
+ */
+PQExpBuffer
+createPQExpBuffer(void)
+{
+	PQExpBuffer	res;
+
+	res = (PQExpBuffer) malloc(sizeof(PQExpBufferData));
+	if (res != NULL)
+		initPQExpBuffer(res);
+
+	return res;
+}
+
+/*
+ * initPQExpBuffer
+ *
+ * Initialize a PQExpBufferData struct (with previously undefined contents)
+ * to describe an empty string.
+ */
+void
+initPQExpBuffer(PQExpBuffer str)
+{
+	str->data = (char *) malloc(INITIAL_EXPBUFFER_SIZE);
+	if (str->data == NULL)
+	{
+		str->maxlen = 0;
+		str->len = 0;
+	}
+	else
+	{
+		str->maxlen = INITIAL_EXPBUFFER_SIZE;
+		str->len = 0;
+		str->data[0] = '\0';
+	}
+}
+
+/*------------------------
+ * destroyPQExpBuffer(str);
+ *		free()s both the data buffer and the PQExpBufferData.
+ *		This is the inverse of createPQExpBuffer().
+ */
+void
+destroyPQExpBuffer(PQExpBuffer str)
+{
+	if (str)
+	{
+		termPQExpBuffer(str);
+		free(str);
+	}
+}
+
+/*------------------------
+ * termPQExpBuffer(str)
+ *		free()s the data buffer but not the PQExpBufferData itself.
+ *		This is the inverse of initPQExpBuffer().
+ */
+void
+termPQExpBuffer(PQExpBuffer str)
+{
+	if (str->data)
+	{
+		free(str->data);
+		str->data = NULL;
+	}
+}
+
+/*------------------------
+ * resetPQExpBuffer
+ *		Reset a PQExpBuffer to empty
+ */
+void
+resetPQExpBuffer(PQExpBuffer str)
+{
+	if (str)
+	{
+		str->len = 0;
+		if (str->data)
+			str->data[0] = '\0';
+	}
+}
+
+/*------------------------
+ * enlargePQExpBuffer
+ * Make sure there is enough space for 'needed' more bytes in the buffer
+ * ('needed' does not include the terminating null).
+ *
+ * Returns 1 if OK, 0 if failed to enlarge buffer.
+ */
+int
+enlargePQExpBuffer(PQExpBuffer str, int needed)
+{
+	int			newlen;
+	char	   *newdata;
+
+	needed += str->len + 1;		/* total space required now */
+	if (needed <= str->maxlen)
+		return 1;				/* got enough space already */
+
+	/*
+	 * We don't want to allocate just a little more space with each
+	 * append; for efficiency, double the buffer size each time it
+	 * overflows. Actually, we might need to more than double it if
+	 * 'needed' is big...
+	 */
+	newlen = str->maxlen ? (2 * str->maxlen) : 64;
+	while (needed > newlen)
+		newlen = 2 * newlen;
+
+	newdata = (char *) realloc(str->data, newlen);
+	if (newdata != NULL)
+	{
+		str->data = newdata;
+		str->maxlen = newlen;
+		return 1;
+	}
+	return 0;
+}
+
+/*------------------------
+ * printfPQExpBuffer
+ * Format text data under the control of fmt (an sprintf-like format string)
+ * and insert it into str.  More space is allocated to str if necessary.
+ * This is a convenience routine that does the same thing as
+ * resetPQExpBuffer() followed by appendPQExpBuffer().
+ *
+ * CAUTION: the frontend version of this routine WILL FAIL if the result of
+ * the sprintf formatting operation exceeds 1KB of data (but the size of the
+ * pre-existing string in the buffer doesn't matter).  We could make it
+ * support larger strings, but that requires vsnprintf() which is not
+ * universally available.  Currently there is no need for long strings to be
+ * formatted in the frontend.  We could support it, if necessary, by
+ * conditionally including a vsnprintf emulation.
+ */
+void
+printfPQExpBuffer(PQExpBuffer str, const char *fmt,...)
+{
+	va_list		args;
+	char		buffer[1024];
+
+	va_start(args, fmt);
+	vsprintf(buffer, fmt, args);
+	va_end(args);
+
+	resetPQExpBuffer(str);
+	appendPQExpBufferStr(str, buffer);
+}
+
+/*------------------------
+ * appendPQExpBuffer
+ *
+ * Format text data under the control of fmt (an sprintf-like format string)
+ * and append it to whatever is already in str.  More space is allocated
+ * to str if necessary.  This is sort of like a combination of sprintf and
+ * strcat.
+ *
+ * CAUTION: the frontend version of this routine WILL FAIL if the result of
+ * the sprintf formatting operation exceeds 1KB of data (but the size of the
+ * pre-existing string in the buffer doesn't matter).  We could make it
+ * support larger strings, but that requires vsnprintf() which is not
+ * universally available.  Currently there is no need for long strings to be
+ * formatted in the frontend.  We could support it, if necessary, by
+ * conditionally including a vsnprintf emulation.
+ */
+void
+appendPQExpBuffer(PQExpBuffer str, const char *fmt,...)
+{
+	va_list		args;
+	char		buffer[1024];
+
+	va_start(args, fmt);
+	vsprintf(buffer, fmt, args);
+	va_end(args);
+
+	appendPQExpBufferStr(str, buffer);
+}
+
+/*------------------------
+ * appendPQExpBufferStr
+ * Append the given string to a PQExpBuffer, allocating more space
+ * if necessary.
+ */
+void
+appendPQExpBufferStr(PQExpBuffer str, const char *data)
+{
+	appendBinaryPQExpBuffer(str, data, strlen(data));
+}
+
+/*------------------------
+ * appendPQExpBufferChar
+ * Append a single byte to str.
+ * Like appendPQExpBuffer(str, "%c", ch) but much faster.
+ */
+void
+appendPQExpBufferChar(PQExpBuffer str, char ch)
+{
+	/* Make more room if needed */
+	if (! enlargePQExpBuffer(str, 1))
+		return;
+
+	/* OK, append the character */
+	str->data[str->len] = ch;
+	str->len++;
+	str->data[str->len] = '\0';
+}
+
+/*
+ * appendBinaryPQExpBuffer
+ *
+ * Append arbitrary binary data to a PQExpBuffer, allocating more space
+ * if necessary.
+ */
+void
+appendBinaryPQExpBuffer(PQExpBuffer str, const char *data, int datalen)
+{
+	/* Make more room if needed */
+	if (! enlargePQExpBuffer(str, datalen))
+		return;
+
+	/* OK, append the data */
+	memcpy(str->data + str->len, data, datalen);
+	str->len += datalen;
+
+	/*
+	 * Keep a trailing null in place, even though it's probably useless
+	 * for binary data...
+	 */
+	str->data[str->len] = '\0';
+}
diff --git a/src/interfaces/libpq/pqexpbuffer.h b/src/interfaces/libpq/pqexpbuffer.h
new file mode 100644
index 00000000000..5c2d2f122ce
--- /dev/null
+++ b/src/interfaces/libpq/pqexpbuffer.h
@@ -0,0 +1,169 @@
+/*-------------------------------------------------------------------------
+ *
+ * pqexpbuffer.h
+ *	  Declarations/definitions for "PQExpBuffer" functions.
+ *
+ * PQExpBuffer provides an indefinitely-extensible string data type.
+ * It can be used to buffer either ordinary C strings (null-terminated text)
+ * or arbitrary binary data.  All storage is allocated with malloc().
+ *
+ * This module is essentially the same as the backend's StringInfo data type,
+ * but it is intended for use in frontend libpq and client applications.
+ * Thus, it does not rely on palloc(), elog(), nor vsnprintf().
+ *
+ * Copyright (c) 1994, Regents of the University of California
+ *
+ * $Id: pqexpbuffer.h,v 1.1 1999/08/31 01:37:37 tgl Exp $
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PQEXPBUFFER_H
+#define PQEXPBUFFER_H
+
+/*-------------------------
+ * PQExpBufferData holds information about an extensible string.
+ *		data	is the current buffer for the string (allocated with malloc).
+ *		len		is the current string length.  There is guaranteed to be
+ *				a terminating '\0' at data[len], although this is not very
+ *				useful when the string holds binary data rather than text.
+ *		maxlen	is the allocated size in bytes of 'data', i.e. the maximum
+ *				string size (including the terminating '\0' char) that we can
+ *				currently store in 'data' without having to reallocate
+ *				more space.  We must always have maxlen > len.
+ *-------------------------
+ */
+typedef struct PQExpBufferData
+{
+	char	   *data;
+	int			len;
+	int			maxlen;
+} PQExpBufferData;
+
+typedef PQExpBufferData *PQExpBuffer;
+
+/*------------------------
+ * Initial size of the data buffer in a PQExpBuffer.
+ * NB: this must be large enough to hold error messages that might
+ * be returned by PQrequestCancel() or any routine in fe-auth.c.
+ *------------------------
+ */
+#define INITIAL_EXPBUFFER_SIZE  256
+
+/*------------------------
+ * There are two ways to create a PQExpBuffer object initially:
+ *
+ * PQExpBuffer stringptr = createPQExpBuffer();
+ *		Both the PQExpBufferData and the data buffer are malloc'd.
+ *
+ * PQExpBufferData string;
+ * initPQExpBuffer(&string);
+ *		The data buffer is malloc'd but the PQExpBufferData is presupplied.
+ *		This is appropriate if the PQExpBufferData is a field of another
+ *		struct.
+ *-------------------------
+ */
+
+/*------------------------
+ * createPQExpBuffer
+ * Create an empty 'PQExpBufferData' & return a pointer to it.
+ */
+extern PQExpBuffer createPQExpBuffer(void);
+
+/*------------------------
+ * initPQExpBuffer
+ * Initialize a PQExpBufferData struct (with previously undefined contents)
+ * to describe an empty string.
+ */
+extern void initPQExpBuffer(PQExpBuffer str);
+
+/*------------------------
+ * To destroy a PQExpBuffer, use either:
+ *
+ * destroyPQExpBuffer(str);
+ *		free()s both the data buffer and the PQExpBufferData.
+ *		This is the inverse of createPQExpBuffer().
+ *
+ * termPQExpBuffer(str)
+ *		free()s the data buffer but not the PQExpBufferData itself.
+ *		This is the inverse of initPQExpBuffer().
+ *
+ * NOTE: some routines build up a string using PQExpBuffer, and then
+ * release the PQExpBufferData but return the data string itself to their
+ * caller.	At that point the data string looks like a plain malloc'd
+ * string.
+ */
+extern void destroyPQExpBuffer(PQExpBuffer str);
+extern void termPQExpBuffer(PQExpBuffer str);
+
+/*------------------------
+ * resetPQExpBuffer
+ *		Reset a PQExpBuffer to empty
+ */
+extern void resetPQExpBuffer(PQExpBuffer str);
+
+/*------------------------
+ * enlargePQExpBuffer
+ * Make sure there is enough space for 'needed' more bytes in the buffer
+ * ('needed' does not include the terminating null).
+ *
+ * Returns 1 if OK, 0 if failed to enlarge buffer.
+ */
+extern int enlargePQExpBuffer(PQExpBuffer str, int needed);
+
+/*------------------------
+ * printfPQExpBuffer
+ * Format text data under the control of fmt (an sprintf-like format string)
+ * and insert it into str.  More space is allocated to str if necessary.
+ * This is a convenience routine that does the same thing as
+ * resetPQExpBuffer() followed by appendPQExpBuffer().
+ *
+ * CAUTION: the frontend version of this routine WILL FAIL if the result of
+ * the sprintf formatting operation exceeds 1KB of data (but the size of the
+ * pre-existing string in the buffer doesn't matter).  We could make it
+ * support larger strings, but that requires vsnprintf() which is not
+ * universally available.  Currently there is no need for long strings to be
+ * formatted in the frontend.  We could support it, if necessary, by
+ * conditionally including a vsnprintf emulation.
+ */
+extern void printfPQExpBuffer(PQExpBuffer str, const char *fmt,...);
+
+/*------------------------
+ * appendPQExpBuffer
+ * Format text data under the control of fmt (an sprintf-like format string)
+ * and append it to whatever is already in str.  More space is allocated
+ * to str if necessary.  This is sort of like a combination of sprintf and
+ * strcat.
+ *
+ * CAUTION: the frontend version of this routine WILL FAIL if the result of
+ * the sprintf formatting operation exceeds 1KB of data (but the size of the
+ * pre-existing string in the buffer doesn't matter).  We could make it
+ * support larger strings, but that requires vsnprintf() which is not
+ * universally available.  Currently there is no need for long strings to be
+ * formatted in the frontend.  We could support it, if necessary, by
+ * conditionally including a vsnprintf emulation.
+ */
+extern void appendPQExpBuffer(PQExpBuffer str, const char *fmt,...);
+
+/*------------------------
+ * appendPQExpBufferStr
+ * Append the given string to a PQExpBuffer, allocating more space
+ * if necessary.
+ */
+extern void appendPQExpBufferStr(PQExpBuffer str, const char *data);
+
+/*------------------------
+ * appendPQExpBufferChar
+ * Append a single byte to str.
+ * Like appendPQExpBuffer(str, "%c", ch) but much faster.
+ */
+extern void appendPQExpBufferChar(PQExpBuffer str, char ch);
+
+/*------------------------
+ * appendBinaryPQExpBuffer
+ * Append arbitrary binary data to a PQExpBuffer, allocating more space
+ * if necessary.
+ */
+extern void appendBinaryPQExpBuffer(PQExpBuffer str,
+					   const char *data, int datalen);
+
+#endif	 /* PQEXPBUFFER_H */
diff --git a/src/interfaces/libpq/win32.mak b/src/interfaces/libpq/win32.mak
index 1d351df7e8f..eda99d815ba 100644
--- a/src/interfaces/libpq/win32.mak
+++ b/src/interfaces/libpq/win32.mak
@@ -29,6 +29,7 @@ CLEAN :
 	-@erase "$(INTDIR)\fe-lobj.obj"
 	-@erase "$(INTDIR)\fe-misc.obj"
 	-@erase "$(INTDIR)\fe-print.obj"
+	-@erase "$(INTDIR)\pqexpbuffer.obj"
 	-@erase "$(OUTDIR)\libpqdll.obj"
 	-@erase "$(OUTDIR)\libpq.lib"
 	-@erase "$(OUTDIR)\libpq.dll"
@@ -70,7 +71,8 @@ LIB32_OBJS= \
 	"$(INTDIR)\fe-exec.obj" \
 	"$(INTDIR)\fe-lobj.obj" \
 	"$(INTDIR)\fe-misc.obj" \
-	"$(INTDIR)\fe-print.obj"
+	"$(INTDIR)\fe-print.obj" \
+	"$(INTDIR)\pqexpbuffer.obj"
 
 !IFDEF MULTIBYTE
 LIB32_OBJS = $(LIB32_OBJS) "$(INTDIR)\common.obj" "$(INTDIR)\wchar.obj" "$(INTDIR)\conv.obj" "$(INTDIR)\big5.obj"
-- 
GitLab