libpq error message refactoring

Started by Peter Eisentrautover 3 years ago14 messages
#1Peter Eisentraut
peter.eisentraut@enterprisedb.com
1 attachment(s)

libpq now contains a mix of error message strings that end with newlines
and don't end with newlines, due to some newer code paths with new ways
of passing errors around. This has now gotten me confused a few too
many times both during development and translation. So I looked into
whether we can unify this, similar to how we have done elsewhere (e.g.,
pg_upgrade). I came up with the attached patch. It's not complete, but
it shows the idea and it looks like a nice simplification to me.
Thoughts on this approach?

Attachments:

v1-0001-WIP-libpq_append_error.patchtext/plain; charset=UTF-8; name=v1-0001-WIP-libpq_append_error.patchDownload
From e547d9993232e20c9e696bd7facbcb3de4afdd6c Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter@eisentraut.org>
Date: Thu, 4 Aug 2022 23:31:58 +0300
Subject: [PATCH v1] WIP: libpq_append_error

---
 src/interfaces/libpq/fe-connect.c  | 271 +++++++++++------------------
 src/interfaces/libpq/fe-exec.c     | 109 ++++--------
 src/interfaces/libpq/fe-misc.c     |  15 ++
 src/interfaces/libpq/libpq-int.h   |   2 +
 src/interfaces/libpq/nls.mk        |   4 +-
 src/interfaces/libpq/pqexpbuffer.c |  27 ++-
 src/interfaces/libpq/pqexpbuffer.h |   2 +
 7 files changed, 186 insertions(+), 244 deletions(-)

diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index 8cefef20d1..79b9392858 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -896,8 +896,7 @@ fillPGconn(PGconn *conn, PQconninfoOption *connOptions)
 				*connmember = strdup(tmp);
 				if (*connmember == NULL)
 				{
-					appendPQExpBufferStr(&conn->errorMessage,
-										 libpq_gettext("out of memory\n"));
+					libpq_append_error(conn, "out of memory");
 					return false;
 				}
 			}
@@ -1079,9 +1078,8 @@ connectOptions2(PGconn *conn)
 		if (more || i != conn->nconnhost)
 		{
 			conn->status = CONNECTION_BAD;
-			appendPQExpBuffer(&conn->errorMessage,
-							  libpq_gettext("could not match %d host names to %d hostaddr values\n"),
-							  count_comma_separated_elems(conn->pghost), conn->nconnhost);
+			libpq_append_error(conn, "could not match %d host names to %d hostaddr values",
+							   count_comma_separated_elems(conn->pghost), conn->nconnhost);
 			return false;
 		}
 	}
@@ -1160,9 +1158,8 @@ connectOptions2(PGconn *conn)
 		else if (more || i != conn->nconnhost)
 		{
 			conn->status = CONNECTION_BAD;
-			appendPQExpBuffer(&conn->errorMessage,
-							  libpq_gettext("could not match %d port numbers to %d hosts\n"),
-							  count_comma_separated_elems(conn->pgport), conn->nconnhost);
+			libpq_append_error(conn, "could not match %d port numbers to %d hosts",
+							   count_comma_separated_elems(conn->pgport), conn->nconnhost);
 			return false;
 		}
 	}
@@ -1250,9 +1247,8 @@ connectOptions2(PGconn *conn)
 			&& strcmp(conn->channel_binding, "require") != 0)
 		{
 			conn->status = CONNECTION_BAD;
-			appendPQExpBuffer(&conn->errorMessage,
-							  libpq_gettext("invalid %s value: \"%s\"\n"),
-							  "channel_binding", conn->channel_binding);
+			libpq_append_error(conn, "invalid %s value: \"%s\"",
+							   "channel_binding", conn->channel_binding);
 			return false;
 		}
 	}
@@ -1276,9 +1272,8 @@ connectOptions2(PGconn *conn)
 			&& strcmp(conn->sslmode, "verify-full") != 0)
 		{
 			conn->status = CONNECTION_BAD;
-			appendPQExpBuffer(&conn->errorMessage,
-							  libpq_gettext("invalid %s value: \"%s\"\n"),
-							  "sslmode", conn->sslmode);
+			libpq_append_error(conn, "invalid %s value: \"%s\"",
+							   "sslmode", conn->sslmode);
 			return false;
 		}
 
@@ -1297,9 +1292,8 @@ connectOptions2(PGconn *conn)
 			case 'r':			/* "require" */
 			case 'v':			/* "verify-ca" or "verify-full" */
 				conn->status = CONNECTION_BAD;
-				appendPQExpBuffer(&conn->errorMessage,
-								  libpq_gettext("sslmode value \"%s\" invalid when SSL support is not compiled in\n"),
-								  conn->sslmode);
+				libpq_append_error(conn, "sslmode value \"%s\" invalid when SSL support is not compiled in",
+								   conn->sslmode);
 				return false;
 		}
 #endif
@@ -1318,19 +1312,17 @@ connectOptions2(PGconn *conn)
 	if (!sslVerifyProtocolVersion(conn->ssl_min_protocol_version))
 	{
 		conn->status = CONNECTION_BAD;
-		appendPQExpBuffer(&conn->errorMessage,
-						  libpq_gettext("invalid %s value: \"%s\"\n"),
-						  "ssl_min_protocol_version",
-						  conn->ssl_min_protocol_version);
+		libpq_append_error(conn, "invalid %s value: \"%s\"",
+						   "ssl_min_protocol_version",
+						   conn->ssl_min_protocol_version);
 		return false;
 	}
 	if (!sslVerifyProtocolVersion(conn->ssl_max_protocol_version))
 	{
 		conn->status = CONNECTION_BAD;
-		appendPQExpBuffer(&conn->errorMessage,
-						  libpq_gettext("invalid %s value: \"%s\"\n"),
-						  "ssl_max_protocol_version",
-						  conn->ssl_max_protocol_version);
+		libpq_append_error(conn, "invalid %s value: \"%s\"",
+						   "ssl_max_protocol_version",
+						   conn->ssl_max_protocol_version);
 		return false;
 	}
 
@@ -1345,8 +1337,7 @@ connectOptions2(PGconn *conn)
 								conn->ssl_max_protocol_version))
 	{
 		conn->status = CONNECTION_BAD;
-		appendPQExpBufferStr(&conn->errorMessage,
-							 libpq_gettext("invalid SSL protocol version range\n"));
+		libpq_append_error(conn, "invalid SSL protocol version range");
 		return false;
 	}
 
@@ -1360,19 +1351,15 @@ connectOptions2(PGconn *conn)
 			strcmp(conn->gssencmode, "require") != 0)
 		{
 			conn->status = CONNECTION_BAD;
-			appendPQExpBuffer(&conn->errorMessage,
-							  libpq_gettext("invalid %s value: \"%s\"\n"),
-							  "gssencmode",
-							  conn->gssencmode);
+			libpq_append_error(conn, "invalid %s value: \"%s\"", "gssencmode", conn->gssencmode);
 			return false;
 		}
 #ifndef ENABLE_GSS
 		if (strcmp(conn->gssencmode, "require") == 0)
 		{
 			conn->status = CONNECTION_BAD;
-			appendPQExpBuffer(&conn->errorMessage,
-							  libpq_gettext("gssencmode value \"%s\" invalid when GSSAPI support is not compiled in\n"),
-							  conn->gssencmode);
+			libpq_append_error(conn, "gssencmode value \"%s\" invalid when GSSAPI support is not compiled in",
+							   conn->gssencmode);
 			return false;
 		}
 #endif
@@ -1404,10 +1391,9 @@ connectOptions2(PGconn *conn)
 		else
 		{
 			conn->status = CONNECTION_BAD;
-			appendPQExpBuffer(&conn->errorMessage,
-							  libpq_gettext("invalid %s value: \"%s\"\n"),
-							  "target_session_attrs",
-							  conn->target_session_attrs);
+			libpq_append_error(conn, "invalid %s value: \"%s\"",
+							   "target_session_attrs",
+							   conn->target_session_attrs);
 			return false;
 		}
 	}
@@ -1437,8 +1423,7 @@ connectOptions2(PGconn *conn)
 
 oom_error:
 	conn->status = CONNECTION_BAD;
-	appendPQExpBufferStr(&conn->errorMessage,
-						 libpq_gettext("out of memory\n"));
+	libpq_append_error(conn, "out of memory");
 	return false;
 }
 
@@ -1600,8 +1585,7 @@ PQsetdbLogin(const char *pghost, const char *pgport, const char *pgoptions,
 
 oom_error:
 	conn->status = CONNECTION_BAD;
-	appendPQExpBufferStr(&conn->errorMessage,
-						 libpq_gettext("out of memory\n"));
+	libpq_append_error(conn, "out of memory");
 	return conn;
 }
 
@@ -1624,9 +1608,8 @@ connectNoDelay(PGconn *conn)
 	{
 		char		sebuf[PG_STRERROR_R_BUFLEN];
 
-		appendPQExpBuffer(&conn->errorMessage,
-						  libpq_gettext("could not set socket to TCP no delay mode: %s\n"),
-						  SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof(sebuf)));
+		libpq_append_error(conn, "could not set socket to TCP no delay mode: %s",
+						   SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof(sebuf)));
 		return 0;
 	}
 #endif
@@ -1740,11 +1723,9 @@ connectFailureMessage(PGconn *conn, int errorno)
 					  SOCK_STRERROR(errorno, sebuf, sizeof(sebuf)));
 
 	if (conn->raddr.addr.ss_family == AF_UNIX)
-		appendPQExpBufferStr(&conn->errorMessage,
-							 libpq_gettext("\tIs the server running locally and accepting connections on that socket?\n"));
+		libpq_append_error(conn, "\tIs the server running locally and accepting connections on that socket?");
 	else
-		appendPQExpBufferStr(&conn->errorMessage,
-							 libpq_gettext("\tIs the server running on that host and accepting TCP/IP connections?\n"));
+		libpq_append_error(conn, "\tIs the server running on that host and accepting TCP/IP connections?");
 }
 
 /*
@@ -1807,9 +1788,8 @@ parse_int_param(const char *value, int *result, PGconn *conn,
 	return true;
 
 error:
-	appendPQExpBuffer(&conn->errorMessage,
-					  libpq_gettext("invalid integer value \"%s\" for connection option \"%s\"\n"),
-					  value, context);
+	libpq_append_error(conn, "invalid integer value \"%s\" for connection option \"%s\"",
+					   value, context);
 	return false;
 }
 
@@ -1837,11 +1817,10 @@ setKeepalivesIdle(PGconn *conn)
 	{
 		char		sebuf[PG_STRERROR_R_BUFLEN];
 
-		appendPQExpBuffer(&conn->errorMessage,
-						  libpq_gettext("%s(%s) failed: %s\n"),
-						  "setsockopt",
-						  PG_TCP_KEEPALIVE_IDLE_STR,
-						  SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof(sebuf)));
+		libpq_append_error(conn, "%s(%s) failed: %s",
+						   "setsockopt",
+						   PG_TCP_KEEPALIVE_IDLE_STR,
+						   SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof(sebuf)));
 		return 0;
 	}
 #endif
@@ -1872,11 +1851,10 @@ setKeepalivesInterval(PGconn *conn)
 	{
 		char		sebuf[PG_STRERROR_R_BUFLEN];
 
-		appendPQExpBuffer(&conn->errorMessage,
-						  libpq_gettext("%s(%s) failed: %s\n"),
-						  "setsockopt",
-						  "TCP_KEEPINTVL",
-						  SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof(sebuf)));
+		libpq_append_error(conn, "%s(%s) failed: %s",
+						   "setsockopt",
+						   "TCP_KEEPINTVL",
+						   SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof(sebuf)));
 		return 0;
 	}
 #endif
@@ -1908,11 +1886,10 @@ setKeepalivesCount(PGconn *conn)
 	{
 		char		sebuf[PG_STRERROR_R_BUFLEN];
 
-		appendPQExpBuffer(&conn->errorMessage,
-						  libpq_gettext("%s(%s) failed: %s\n"),
-						  "setsockopt",
-						  "TCP_KEEPCNT",
-						  SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof(sebuf)));
+		libpq_append_error(conn, "%s(%s) failed: %s",
+						   "setsockopt",
+						   "TCP_KEEPCNT",
+						   SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof(sebuf)));
 		return 0;
 	}
 #endif
@@ -1973,8 +1950,7 @@ prepKeepalivesWin32(PGconn *conn)
 
 	if (!setKeepalivesWin32(conn->sock, idle, interval))
 	{
-		appendPQExpBuffer(&conn->errorMessage,
-						  libpq_gettext("%s(%s) failed: error code %d\n"),
+		libpq_append_error(conn, "%s(%s) failed: error code %d",
 						  "WSAIoctl", "SIO_KEEPALIVE_VALS",
 						  WSAGetLastError());
 		return 0;
@@ -2008,11 +1984,10 @@ setTCPUserTimeout(PGconn *conn)
 	{
 		char		sebuf[256];
 
-		appendPQExpBuffer(&conn->errorMessage,
-						  libpq_gettext("%s(%s) failed: %s\n"),
-						  "setsockopt",
-						  "TCP_USER_TIMEOUT",
-						  SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof(sebuf)));
+		libpq_append_error(conn, "%s(%s) failed: %s",
+						   "setsockopt",
+						   "TCP_USER_TIMEOUT",
+						   SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof(sebuf)));
 		return 0;
 	}
 #endif
@@ -2288,8 +2263,7 @@ PQconnectPoll(PGconn *conn)
 			break;
 
 		default:
-			appendPQExpBufferStr(&conn->errorMessage,
-								 libpq_gettext("invalid connection state, probably indicative of memory corruption\n"));
+			libpq_append_error(conn, "invalid connection state, probably indicative of memory corruption");
 			goto error_return;
 	}
 
@@ -2367,9 +2341,7 @@ PQconnectPoll(PGconn *conn)
 
 			if (thisport < 1 || thisport > 65535)
 			{
-				appendPQExpBuffer(&conn->errorMessage,
-								  libpq_gettext("invalid port number: \"%s\"\n"),
-								  ch->port);
+				libpq_append_error(conn, "invalid port number: \"%s\"", ch->port);
 				goto keep_going;
 			}
 		}
@@ -2383,9 +2355,8 @@ PQconnectPoll(PGconn *conn)
 										 &conn->addrlist);
 				if (ret || !conn->addrlist)
 				{
-					appendPQExpBuffer(&conn->errorMessage,
-									  libpq_gettext("could not translate host name \"%s\" to address: %s\n"),
-									  ch->host, gai_strerror(ret));
+					libpq_append_error(conn, "could not translate host name \"%s\" to address: %s",
+									   ch->host, gai_strerror(ret));
 					goto keep_going;
 				}
 				break;
@@ -2396,9 +2367,8 @@ PQconnectPoll(PGconn *conn)
 										 &conn->addrlist);
 				if (ret || !conn->addrlist)
 				{
-					appendPQExpBuffer(&conn->errorMessage,
-									  libpq_gettext("could not parse network address \"%s\": %s\n"),
-									  ch->hostaddr, gai_strerror(ret));
+					libpq_append_error(conn, "could not parse network address \"%s\": %s",
+									   ch->hostaddr, gai_strerror(ret));
 					goto keep_going;
 				}
 				break;
@@ -2408,10 +2378,9 @@ PQconnectPoll(PGconn *conn)
 				UNIXSOCK_PATH(portstr, thisport, ch->host);
 				if (strlen(portstr) >= UNIXSOCK_PATH_BUFLEN)
 				{
-					appendPQExpBuffer(&conn->errorMessage,
-									  libpq_gettext("Unix-domain socket path \"%s\" is too long (maximum %d bytes)\n"),
-									  portstr,
-									  (int) (UNIXSOCK_PATH_BUFLEN - 1));
+					libpq_append_error(conn, "Unix-domain socket path \"%s\" is too long (maximum %d bytes)",
+									   portstr,
+									   (int) (UNIXSOCK_PATH_BUFLEN - 1));
 					goto keep_going;
 				}
 
@@ -2423,9 +2392,8 @@ PQconnectPoll(PGconn *conn)
 										 &conn->addrlist);
 				if (ret || !conn->addrlist)
 				{
-					appendPQExpBuffer(&conn->errorMessage,
-									  libpq_gettext("could not translate Unix-domain socket path \"%s\" to address: %s\n"),
-									  portstr, gai_strerror(ret));
+					libpq_append_error(conn, "could not translate Unix-domain socket path \"%s\" to address: %s",
+									   portstr, gai_strerror(ret));
 					goto keep_going;
 				}
 				break;
@@ -2546,9 +2514,8 @@ PQconnectPoll(PGconn *conn)
 							goto keep_going;
 						}
 						emitHostIdentityInfo(conn, host_addr);
-						appendPQExpBuffer(&conn->errorMessage,
-										  libpq_gettext("could not create socket: %s\n"),
-										  SOCK_STRERROR(errorno, sebuf, sizeof(sebuf)));
+						libpq_append_error(conn, "could not create socket: %s",
+										   SOCK_STRERROR(errorno, sebuf, sizeof(sebuf)));
 						goto error_return;
 					}
 
@@ -2577,9 +2544,8 @@ PQconnectPoll(PGconn *conn)
 					}
 					if (!pg_set_noblock(conn->sock))
 					{
-						appendPQExpBuffer(&conn->errorMessage,
-										  libpq_gettext("could not set socket to nonblocking mode: %s\n"),
-										  SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof(sebuf)));
+						libpq_append_error(conn, "could not set socket to nonblocking mode: %s",
+										   SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof(sebuf)));
 						conn->try_next_addr = true;
 						goto keep_going;
 					}
@@ -2587,9 +2553,8 @@ PQconnectPoll(PGconn *conn)
 #ifdef F_SETFD
 					if (fcntl(conn->sock, F_SETFD, FD_CLOEXEC) == -1)
 					{
-						appendPQExpBuffer(&conn->errorMessage,
-										  libpq_gettext("could not set socket to close-on-exec mode: %s\n"),
-										  SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof(sebuf)));
+						libpq_append_error(conn, "could not set socket to close-on-exec mode: %s",
+										   SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof(sebuf)));
 						conn->try_next_addr = true;
 						goto keep_going;
 					}
@@ -2605,8 +2570,7 @@ PQconnectPoll(PGconn *conn)
 
 						if (usekeepalives < 0)
 						{
-							appendPQExpBufferStr(&conn->errorMessage,
-												 libpq_gettext("keepalives parameter must be an integer\n"));
+							libpq_append_error(conn, "keepalives parameter must be an integer");
 							err = 1;
 						}
 						else if (usekeepalives == 0)
@@ -2618,11 +2582,10 @@ PQconnectPoll(PGconn *conn)
 											SOL_SOCKET, SO_KEEPALIVE,
 											(char *) &on, sizeof(on)) < 0)
 						{
-							appendPQExpBuffer(&conn->errorMessage,
-											  libpq_gettext("%s(%s) failed: %s\n"),
-											  "setsockopt",
-											  "SO_KEEPALIVE",
-											  SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof(sebuf)));
+							libpq_append_error(conn, "%s(%s) failed: %s",
+											   "setsockopt",
+											   "SO_KEEPALIVE",
+											   SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof(sebuf)));
 							err = 1;
 						}
 						else if (!setKeepalivesIdle(conn)
@@ -2746,9 +2709,8 @@ PQconnectPoll(PGconn *conn)
 				if (getsockopt(conn->sock, SOL_SOCKET, SO_ERROR,
 							   (char *) &optval, &optlen) == -1)
 				{
-					appendPQExpBuffer(&conn->errorMessage,
-									  libpq_gettext("could not get socket error status: %s\n"),
-									  SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof(sebuf)));
+					libpq_append_error(conn, "could not get socket error status: %s",
+									   SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof(sebuf)));
 					goto error_return;
 				}
 				else if (optval != 0)
@@ -2774,9 +2736,8 @@ PQconnectPoll(PGconn *conn)
 								(struct sockaddr *) &conn->laddr.addr,
 								&conn->laddr.salen) < 0)
 				{
-					appendPQExpBuffer(&conn->errorMessage,
-									  libpq_gettext("could not get client address from socket: %s\n"),
-									  SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof(sebuf)));
+					libpq_append_error(conn, "could not get client address from socket: %s",
+									   SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof(sebuf)));
 					goto error_return;
 				}
 
@@ -2813,12 +2774,10 @@ PQconnectPoll(PGconn *conn)
 						 * stub
 						 */
 						if (errno == ENOSYS)
-							appendPQExpBufferStr(&conn->errorMessage,
-												 libpq_gettext("requirepeer parameter is not supported on this platform\n"));
+							libpq_append_error(conn, "requirepeer parameter is not supported on this platform");
 						else
-							appendPQExpBuffer(&conn->errorMessage,
-											  libpq_gettext("could not get peer credentials: %s\n"),
-											  strerror_r(errno, sebuf, sizeof(sebuf)));
+							libpq_append_error(conn, "could not get peer credentials: %s",
+											   strerror_r(errno, sebuf, sizeof(sebuf)));
 						goto error_return;
 					}
 
@@ -2830,9 +2789,8 @@ PQconnectPoll(PGconn *conn)
 
 					if (strcmp(remote_username, conn->requirepeer) != 0)
 					{
-						appendPQExpBuffer(&conn->errorMessage,
-										  libpq_gettext("requirepeer specifies \"%s\", but actual peer user name is \"%s\"\n"),
-										  conn->requirepeer, remote_username);
+						libpq_append_error(conn, "requirepeer specifies \"%s\", but actual peer user name is \"%s\"",
+										   conn->requirepeer, remote_username);
 						free(remote_username);
 						goto error_return;
 					}
@@ -2872,9 +2830,8 @@ PQconnectPoll(PGconn *conn)
 
 					if (pqPacketSend(conn, 0, &pv, sizeof(pv)) != STATUS_OK)
 					{
-						appendPQExpBuffer(&conn->errorMessage,
-										  libpq_gettext("could not send GSSAPI negotiation packet: %s\n"),
-										  SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof(sebuf)));
+						libpq_append_error(conn, "could not send GSSAPI negotiation packet: %s",
+										   SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof(sebuf)));
 						goto error_return;
 					}
 
@@ -2884,8 +2841,8 @@ PQconnectPoll(PGconn *conn)
 				}
 				else if (!conn->gctx && conn->gssencmode[0] == 'r')
 				{
-					appendPQExpBufferStr(&conn->errorMessage,
-										 libpq_gettext("GSSAPI encryption required but was impossible (possibly no credential cache, no server support, or using a local socket)\n"));
+					libpq_append_error(conn,
+									   "GSSAPI encryption required but was impossible (possibly no credential cache, no server support, or using a local socket)");
 					goto error_return;
 				}
 #endif
@@ -2926,9 +2883,8 @@ PQconnectPoll(PGconn *conn)
 					pv = pg_hton32(NEGOTIATE_SSL_CODE);
 					if (pqPacketSend(conn, 0, &pv, sizeof(pv)) != STATUS_OK)
 					{
-						appendPQExpBuffer(&conn->errorMessage,
-										  libpq_gettext("could not send SSL negotiation packet: %s\n"),
-										  SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof(sebuf)));
+						libpq_append_error(conn, "could not send SSL negotiation packet: %s",
+										   SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof(sebuf)));
 						goto error_return;
 					}
 					/* Ok, wait for response */
@@ -2944,8 +2900,7 @@ PQconnectPoll(PGconn *conn)
 													EnvironmentOptions);
 				if (!startpacket)
 				{
-					appendPQExpBufferStr(&conn->errorMessage,
-										 libpq_gettext("out of memory\n"));
+					libpq_append_error(conn, "out of memory");
 					goto error_return;
 				}
 
@@ -2957,9 +2912,8 @@ PQconnectPoll(PGconn *conn)
 				 */
 				if (pqPacketSend(conn, 0, startpacket, packetlen) != STATUS_OK)
 				{
-					appendPQExpBuffer(&conn->errorMessage,
-									  libpq_gettext("could not send startup packet: %s\n"),
-									  SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof(sebuf)));
+					libpq_append_error(conn, "could not send startup packet: %s",
+									   SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof(sebuf)));
 					free(startpacket);
 					goto error_return;
 				}
@@ -3033,8 +2987,7 @@ PQconnectPoll(PGconn *conn)
 														 * "verify-full" */
 						{
 							/* Require SSL, but server does not want it */
-							appendPQExpBufferStr(&conn->errorMessage,
-												 libpq_gettext("server does not support SSL, but SSL was required\n"));
+							libpq_append_error(conn, "server does not support SSL, but SSL was required");
 							goto error_return;
 						}
 						/* Otherwise, proceed with normal startup */
@@ -3060,9 +3013,8 @@ PQconnectPoll(PGconn *conn)
 					}
 					else
 					{
-						appendPQExpBuffer(&conn->errorMessage,
-										  libpq_gettext("received invalid response to SSL negotiation: %c\n"),
-										  SSLok);
+						libpq_append_error(conn, "received invalid response to SSL negotiation: %c",
+										   SSLok);
 						goto error_return;
 					}
 				}
@@ -3081,8 +3033,7 @@ PQconnectPoll(PGconn *conn)
 					 */
 					if (conn->inCursor != conn->inEnd)
 					{
-						appendPQExpBufferStr(&conn->errorMessage,
-											 libpq_gettext("received unencrypted data after SSL response\n"));
+						libpq_append_error(conn, "received unencrypted data after SSL response");
 						goto error_return;
 					}
 
@@ -3162,8 +3113,7 @@ PQconnectPoll(PGconn *conn)
 						/* Server doesn't want GSSAPI; fall back if we can */
 						if (conn->gssencmode[0] == 'r')
 						{
-							appendPQExpBufferStr(&conn->errorMessage,
-												 libpq_gettext("server doesn't support GSSAPI encryption, but it was required\n"));
+							libpq_append_error(conn, "server doesn't support GSSAPI encryption, but it was required");
 							goto error_return;
 						}
 
@@ -3174,9 +3124,8 @@ PQconnectPoll(PGconn *conn)
 					}
 					else if (gss_ok != 'G')
 					{
-						appendPQExpBuffer(&conn->errorMessage,
-										  libpq_gettext("received invalid response to GSSAPI negotiation: %c\n"),
-										  gss_ok);
+						libpq_append_error(conn, "received invalid response to GSSAPI negotiation: %c",
+										   gss_ok);
 						goto error_return;
 					}
 				}
@@ -3193,8 +3142,7 @@ PQconnectPoll(PGconn *conn)
 					 */
 					if (conn->inCursor != conn->inEnd)
 					{
-						appendPQExpBufferStr(&conn->errorMessage,
-											 libpq_gettext("received unencrypted data after GSSAPI encryption response\n"));
+						libpq_append_error(conn, "received unencrypted data after GSSAPI encryption response");
 						goto error_return;
 					}
 
@@ -3253,9 +3201,8 @@ PQconnectPoll(PGconn *conn)
 				 */
 				if (!(beresp == 'R' || beresp == 'E'))
 				{
-					appendPQExpBuffer(&conn->errorMessage,
-									  libpq_gettext("expected authentication request from server, but received %c\n"),
-									  beresp);
+					libpq_append_error(conn, "expected authentication request from server, but received %c",
+									   beresp);
 					goto error_return;
 				}
 
@@ -3278,9 +3225,8 @@ PQconnectPoll(PGconn *conn)
 				 */
 				if (beresp == 'R' && (msgLength < 8 || msgLength > 2000))
 				{
-					appendPQExpBuffer(&conn->errorMessage,
-									  libpq_gettext("expected authentication request from server, but received %c\n"),
-									  beresp);
+					libpq_append_error(conn, "expected authentication request from server, but received %c",
+									   beresp);
 					goto error_return;
 				}
 
@@ -3485,8 +3431,7 @@ PQconnectPoll(PGconn *conn)
 				if (res)
 				{
 					if (res->resultStatus != PGRES_FATAL_ERROR)
-						appendPQExpBufferStr(&conn->errorMessage,
-											 libpq_gettext("unexpected message from server during startup\n"));
+						libpq_append_error(conn, "unexpected message from server during startup");
 					else if (conn->send_appname &&
 							 (conn->appname || conn->fbappname))
 					{
@@ -3577,11 +3522,9 @@ PQconnectPoll(PGconn *conn)
 					{
 						/* Wrong server state, reject and try the next host */
 						if (conn->target_server_type == SERVER_TYPE_READ_WRITE)
-							appendPQExpBufferStr(&conn->errorMessage,
-												 libpq_gettext("session is read-only\n"));
+							libpq_append_error(conn, "session is read-only");
 						else
-							appendPQExpBufferStr(&conn->errorMessage,
-												 libpq_gettext("session is not read-only\n"));
+							libpq_append_error(conn, "session is not read-only");
 
 						/* Close connection politely. */
 						conn->status = CONNECTION_OK;
@@ -3634,11 +3577,9 @@ PQconnectPoll(PGconn *conn)
 					{
 						/* Wrong server state, reject and try the next host */
 						if (conn->target_server_type == SERVER_TYPE_PRIMARY)
-							appendPQExpBufferStr(&conn->errorMessage,
-												 libpq_gettext("server is in hot standby mode\n"));
+							libpq_append_error(conn, "server is in hot standby mode");
 						else
-							appendPQExpBufferStr(&conn->errorMessage,
-												 libpq_gettext("server is not in hot standby mode\n"));
+							libpq_append_error(conn, "server is not in hot standby mode");
 
 						/* Close connection politely. */
 						conn->status = CONNECTION_OK;
diff --git a/src/interfaces/libpq/fe-exec.c b/src/interfaces/libpq/fe-exec.c
index bb874f7f50..28bee2cbed 100644
--- a/src/interfaces/libpq/fe-exec.c
+++ b/src/interfaces/libpq/fe-exec.c
@@ -1316,8 +1316,7 @@ pqAllocCmdQueueEntry(PGconn *conn)
 		entry = (PGcmdQueueEntry *) malloc(sizeof(PGcmdQueueEntry));
 		if (entry == NULL)
 		{
-			appendPQExpBufferStr(&conn->errorMessage,
-								 libpq_gettext("out of memory\n"));
+			libpq_append_error(conn, "out of memory");
 			return NULL;
 		}
 	}
@@ -1441,8 +1440,7 @@ PQsendQueryInternal(PGconn *conn, const char *query, bool newQuery)
 	/* check the argument */
 	if (!query)
 	{
-		appendPQExpBufferStr(&conn->errorMessage,
-							 libpq_gettext("command string is a null pointer\n"));
+		libpq_append_error(conn, "command string is a null pointer");
 		return 0;
 	}
 
@@ -1567,15 +1565,13 @@ PQsendQueryParams(PGconn *conn,
 	/* check the arguments */
 	if (!command)
 	{
-		appendPQExpBufferStr(&conn->errorMessage,
-							 libpq_gettext("command string is a null pointer\n"));
+		libpq_append_error(conn, "command string is a null pointer");
 		return 0;
 	}
 	if (nParams < 0 || nParams > PQ_QUERY_PARAM_MAX_LIMIT)
 	{
-		appendPQExpBuffer(&conn->errorMessage,
-						  libpq_gettext("number of parameters must be between 0 and %d\n"),
-						  PQ_QUERY_PARAM_MAX_LIMIT);
+		libpq_append_error(conn, "number of parameters must be between 0 and %d",
+						   PQ_QUERY_PARAM_MAX_LIMIT);
 		return 0;
 	}
 
@@ -1610,21 +1606,18 @@ PQsendPrepare(PGconn *conn,
 	/* check the arguments */
 	if (!stmtName)
 	{
-		appendPQExpBufferStr(&conn->errorMessage,
-							 libpq_gettext("statement name is a null pointer\n"));
+		libpq_append_error(conn, "statement name is a null pointer");
 		return 0;
 	}
 	if (!query)
 	{
-		appendPQExpBufferStr(&conn->errorMessage,
-							 libpq_gettext("command string is a null pointer\n"));
+		libpq_append_error(conn, "command string is a null pointer");
 		return 0;
 	}
 	if (nParams < 0 || nParams > PQ_QUERY_PARAM_MAX_LIMIT)
 	{
-		appendPQExpBuffer(&conn->errorMessage,
-						  libpq_gettext("number of parameters must be between 0 and %d\n"),
-						  PQ_QUERY_PARAM_MAX_LIMIT);
+		libpq_append_error(conn, "number of parameters must be between 0 and %d",
+						   PQ_QUERY_PARAM_MAX_LIMIT);
 		return 0;
 	}
 
@@ -1712,15 +1705,13 @@ PQsendQueryPrepared(PGconn *conn,
 	/* check the arguments */
 	if (!stmtName)
 	{
-		appendPQExpBufferStr(&conn->errorMessage,
-							 libpq_gettext("statement name is a null pointer\n"));
+		libpq_append_error(conn, "statement name is a null pointer");
 		return 0;
 	}
 	if (nParams < 0 || nParams > PQ_QUERY_PARAM_MAX_LIMIT)
 	{
-		appendPQExpBuffer(&conn->errorMessage,
-						  libpq_gettext("number of parameters must be between 0 and %d\n"),
-						  PQ_QUERY_PARAM_MAX_LIMIT);
+		libpq_append_error(conn, "number of parameters must be between 0 and %d",
+						   PQ_QUERY_PARAM_MAX_LIMIT);
 		return 0;
 	}
 
@@ -1756,8 +1747,7 @@ PQsendQueryStart(PGconn *conn, bool newQuery)
 	/* Don't try to send if we know there's no live connection. */
 	if (conn->status != CONNECTION_OK)
 	{
-		appendPQExpBufferStr(&conn->errorMessage,
-							 libpq_gettext("no connection to the server\n"));
+		libpq_append_error(conn, "no connection to the server");
 		return false;
 	}
 
@@ -1765,8 +1755,7 @@ PQsendQueryStart(PGconn *conn, bool newQuery)
 	if (conn->asyncStatus != PGASYNC_IDLE &&
 		conn->pipelineStatus == PQ_PIPELINE_OFF)
 	{
-		appendPQExpBufferStr(&conn->errorMessage,
-							 libpq_gettext("another command is already in progress\n"));
+		libpq_append_error(conn, "another command is already in progress");
 		return false;
 	}
 
@@ -1796,8 +1785,7 @@ PQsendQueryStart(PGconn *conn, bool newQuery)
 			case PGASYNC_COPY_IN:
 			case PGASYNC_COPY_OUT:
 			case PGASYNC_COPY_BOTH:
-				appendPQExpBufferStr(&conn->errorMessage,
-									 libpq_gettext("cannot queue commands during COPY\n"));
+				libpq_append_error(conn, "cannot queue commands during COPY");
 				return false;
 		}
 	}
@@ -1914,8 +1902,7 @@ PQsendQueryGuts(PGconn *conn,
 					nbytes = paramLengths[i];
 				else
 				{
-					appendPQExpBufferStr(&conn->errorMessage,
-										 libpq_gettext("length must be given for binary parameter\n"));
+					libpq_append_error(conn, "length must be given for binary parameter");
 					goto sendFailed;
 				}
 			}
@@ -2237,9 +2224,7 @@ PQgetResult(PGconn *conn)
 			res = getCopyResult(conn, PGRES_COPY_BOTH);
 			break;
 		default:
-			appendPQExpBuffer(&conn->errorMessage,
-							  libpq_gettext("unexpected asyncStatus: %d\n"),
-							  (int) conn->asyncStatus);
+			libpq_append_error(conn, "unexpected asyncStatus: %d", (int) conn->asyncStatus);
 			pqSaveErrorResult(conn);
 			conn->asyncStatus = PGASYNC_IDLE;	/* try to restore valid state */
 			res = pqPrepareAsyncResult(conn);
@@ -2411,8 +2396,7 @@ PQexecStart(PGconn *conn)
 
 	if (conn->pipelineStatus != PQ_PIPELINE_OFF)
 	{
-		appendPQExpBufferStr(&conn->errorMessage,
-							 libpq_gettext("synchronous command execution functions are not allowed in pipeline mode\n"));
+		libpq_append_error(conn, "synchronous command execution functions are not allowed in pipeline mode");
 		return false;
 	}
 
@@ -2445,8 +2429,7 @@ PQexecStart(PGconn *conn)
 		else if (resultStatus == PGRES_COPY_BOTH)
 		{
 			/* We don't allow PQexec during COPY BOTH */
-			appendPQExpBufferStr(&conn->errorMessage,
-								 libpq_gettext("PQexec not allowed during COPY BOTH\n"));
+			libpq_append_error(conn, "PQexec not allowed during COPY BOTH");
 			return false;
 		}
 		/* check for loss of connection, too */
@@ -2672,8 +2655,7 @@ PQputCopyData(PGconn *conn, const char *buffer, int nbytes)
 	if (conn->asyncStatus != PGASYNC_COPY_IN &&
 		conn->asyncStatus != PGASYNC_COPY_BOTH)
 	{
-		appendPQExpBufferStr(&conn->errorMessage,
-							 libpq_gettext("no COPY in progress\n"));
+		libpq_append_error(conn, "no COPY in progress");
 		return -1;
 	}
 
@@ -2728,8 +2710,7 @@ PQputCopyEnd(PGconn *conn, const char *errormsg)
 	if (conn->asyncStatus != PGASYNC_COPY_IN &&
 		conn->asyncStatus != PGASYNC_COPY_BOTH)
 	{
-		appendPQExpBufferStr(&conn->errorMessage,
-							 libpq_gettext("no COPY in progress\n"));
+		libpq_append_error(conn, "no COPY in progress");
 		return -1;
 	}
 
@@ -2797,8 +2778,7 @@ PQgetCopyData(PGconn *conn, char **buffer, int async)
 	if (conn->asyncStatus != PGASYNC_COPY_OUT &&
 		conn->asyncStatus != PGASYNC_COPY_BOTH)
 	{
-		appendPQExpBufferStr(&conn->errorMessage,
-							 libpq_gettext("no COPY in progress\n"));
+		libpq_append_error(conn, "no COPY in progress");
 		return -2;
 	}
 	return pqGetCopyData3(conn, buffer, async);
@@ -2977,16 +2957,14 @@ PQfn(PGconn *conn,
 
 	if (conn->pipelineStatus != PQ_PIPELINE_OFF)
 	{
-		appendPQExpBufferStr(&conn->errorMessage,
-							 libpq_gettext("PQfn not allowed in pipeline mode\n"));
+		libpq_append_error(conn, "PQfn not allowed in pipeline mode");
 		return NULL;
 	}
 
 	if (conn->sock == PGINVALID_SOCKET || conn->asyncStatus != PGASYNC_IDLE ||
 		pgHavePendingResult(conn))
 	{
-		appendPQExpBufferStr(&conn->errorMessage,
-							 libpq_gettext("connection in wrong state\n"));
+		libpq_append_error(conn, "connection in wrong state");
 		return NULL;
 	}
 
@@ -3029,8 +3007,7 @@ PQenterPipelineMode(PGconn *conn)
 
 	if (conn->asyncStatus != PGASYNC_IDLE)
 	{
-		appendPQExpBufferStr(&conn->errorMessage,
-							 libpq_gettext("cannot enter pipeline mode, connection not idle\n"));
+		libpq_append_error(conn, "cannot enter pipeline mode, connection not idle");
 		return 0;
 	}
 
@@ -3066,13 +3043,11 @@ PQexitPipelineMode(PGconn *conn)
 		case PGASYNC_READY:
 		case PGASYNC_READY_MORE:
 			/* there are some uncollected results */
-			appendPQExpBufferStr(&conn->errorMessage,
-								 libpq_gettext("cannot exit pipeline mode with uncollected results\n"));
+			libpq_append_error(conn, "cannot exit pipeline mode with uncollected results");
 			return 0;
 
 		case PGASYNC_BUSY:
-			appendPQExpBufferStr(&conn->errorMessage,
-								 libpq_gettext("cannot exit pipeline mode while busy\n"));
+			libpq_append_error(conn, "cannot exit pipeline mode while busy");
 			return 0;
 
 		case PGASYNC_IDLE:
@@ -3083,15 +3058,13 @@ PQexitPipelineMode(PGconn *conn)
 		case PGASYNC_COPY_IN:
 		case PGASYNC_COPY_OUT:
 		case PGASYNC_COPY_BOTH:
-			appendPQExpBufferStr(&conn->errorMessage,
-								 libpq_gettext("cannot exit pipeline mode while in COPY\n"));
+			libpq_append_error(conn, "cannot exit pipeline mode while in COPY");
 	}
 
 	/* still work to process */
 	if (conn->cmd_queue_head != NULL)
 	{
-		appendPQExpBufferStr(&conn->errorMessage,
-							 libpq_gettext("cannot exit pipeline mode with uncollected results\n"));
+		libpq_append_error(conn, "cannot exit pipeline mode with uncollected results");
 		return 0;
 	}
 
@@ -3206,8 +3179,7 @@ pqPipelineProcessQueue(PGconn *conn)
 		conn->result = PQmakeEmptyPGresult(conn, PGRES_PIPELINE_ABORTED);
 		if (!conn->result)
 		{
-			appendPQExpBufferStr(&conn->errorMessage,
-								 libpq_gettext("out of memory\n"));
+			libpq_append_error(conn, "out of memory");
 			pqSaveErrorResult(conn);
 			return;
 		}
@@ -3250,8 +3222,7 @@ PQpipelineSync(PGconn *conn)
 
 	if (conn->pipelineStatus == PQ_PIPELINE_OFF)
 	{
-		appendPQExpBufferStr(&conn->errorMessage,
-							 libpq_gettext("cannot send pipeline when not in pipeline mode\n"));
+		libpq_append_error(conn, "cannot send pipeline when not in pipeline mode");
 		return 0;
 	}
 
@@ -3317,8 +3288,7 @@ PQsendFlushRequest(PGconn *conn)
 	/* Don't try to send if we know there's no live connection. */
 	if (conn->status != CONNECTION_OK)
 	{
-		appendPQExpBufferStr(&conn->errorMessage,
-							 libpq_gettext("no connection to the server\n"));
+		libpq_append_error(conn, "no connection to the server");
 		return 0;
 	}
 
@@ -3326,8 +3296,7 @@ PQsendFlushRequest(PGconn *conn)
 	if (conn->asyncStatus != PGASYNC_IDLE &&
 		conn->pipelineStatus == PQ_PIPELINE_OFF)
 	{
-		appendPQExpBufferStr(&conn->errorMessage,
-							 libpq_gettext("another command is already in progress\n"));
+		libpq_append_error(conn, "another command is already in progress");
 		return 0;
 	}
 
@@ -4063,8 +4032,7 @@ PQescapeStringInternal(PGconn *conn,
 			if (error)
 				*error = 1;
 			if (conn)
-				appendPQExpBufferStr(&conn->errorMessage,
-									 libpq_gettext("incomplete multibyte character\n"));
+				libpq_append_error(conn, "incomplete multibyte character");
 			for (; i < len; i++)
 			{
 				if (((size_t) (target - to)) / 2 >= length)
@@ -4154,8 +4122,7 @@ PQescapeInternal(PGconn *conn, const char *str, size_t len, bool as_ident)
 			/* Multibyte character overruns allowable length. */
 			if ((s - str) + charlen > len || memchr(s, 0, charlen) != NULL)
 			{
-				appendPQExpBufferStr(&conn->errorMessage,
-									 libpq_gettext("incomplete multibyte character\n"));
+				libpq_append_error(conn, "incomplete multibyte character");
 				return NULL;
 			}
 
@@ -4172,8 +4139,7 @@ PQescapeInternal(PGconn *conn, const char *str, size_t len, bool as_ident)
 	result = rp = (char *) malloc(result_size);
 	if (rp == NULL)
 	{
-		appendPQExpBufferStr(&conn->errorMessage,
-							 libpq_gettext("out of memory\n"));
+		libpq_append_error(conn, "out of memory");
 		return NULL;
 	}
 
@@ -4337,8 +4303,7 @@ PQescapeByteaInternal(PGconn *conn,
 	if (rp == NULL)
 	{
 		if (conn)
-			appendPQExpBufferStr(&conn->errorMessage,
-								 libpq_gettext("out of memory\n"));
+			libpq_append_error(conn, "out of memory");
 		return NULL;
 	}
 
diff --git a/src/interfaces/libpq/fe-misc.c b/src/interfaces/libpq/fe-misc.c
index 795500c593..f5324401ee 100644
--- a/src/interfaces/libpq/fe-misc.c
+++ b/src/interfaces/libpq/fe-misc.c
@@ -1277,4 +1277,19 @@ libpq_ngettext(const char *msgid, const char *msgid_plural, unsigned long n)
 	return dngettext(PG_TEXTDOMAIN("libpq"), msgid, msgid_plural, n);
 }
 
+void
+libpq_append_error(PGconn *conn, const char *fmt, ...)
+{
+	va_list		args;
+
+	Assert(fmt[strlen(fmt) - 1] != '\n');
+
+	va_start(args, fmt);
+
+	appendPQExpBufferVA(&conn->errorMessage, libpq_gettext(fmt), args);
+	appendPQExpBufferChar(&conn->errorMessage, '\n');
+
+	va_end(args);
+}
+
 #endif							/* ENABLE_NLS */
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index 1eb752a82d..21becf98cc 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -882,6 +882,8 @@ extern char *libpq_ngettext(const char *msgid, const char *msgid_plural, unsigne
 #define libpq_ngettext(s, p, n) ((n) == 1 ? (s) : (p))
 #endif
 
+extern void libpq_append_error(PGconn *conn, const char *fmt, ...) pg_attribute_printf(2, 3);
+
 /*
  * These macros are needed to let error-handling code be portable between
  * Unix and Windows.  (ugh)
diff --git a/src/interfaces/libpq/nls.mk b/src/interfaces/libpq/nls.mk
index 9256b426c1..9d994ac196 100644
--- a/src/interfaces/libpq/nls.mk
+++ b/src/interfaces/libpq/nls.mk
@@ -1,5 +1,5 @@
 # src/interfaces/libpq/nls.mk
 CATALOG_NAME     = libpq
 GETTEXT_FILES    = fe-auth.c fe-auth-scram.c fe-connect.c fe-exec.c fe-gssapi-common.c fe-lobj.c fe-misc.c fe-protocol3.c fe-secure.c fe-secure-common.c fe-secure-gssapi.c fe-secure-openssl.c win32.c ../../port/thread.c
-GETTEXT_TRIGGERS = libpq_gettext pqInternalNotice:2
-GETTEXT_FLAGS    = libpq_gettext:1:pass-c-format pqInternalNotice:2:c-format
+GETTEXT_TRIGGERS = libpq_append_error:2 libpq_gettext pqInternalNotice:2
+GETTEXT_FLAGS    = libpq_append_error:2 libpq_gettext:1:pass-c-format pqInternalNotice:2:c-format
diff --git a/src/interfaces/libpq/pqexpbuffer.c b/src/interfaces/libpq/pqexpbuffer.c
index eb51e6d088..65621ec3b1 100644
--- a/src/interfaces/libpq/pqexpbuffer.c
+++ b/src/interfaces/libpq/pqexpbuffer.c
@@ -40,7 +40,7 @@ static const char oom_buffer[1] = "";
 /* Need a char * for unconstify() compatibility */
 static const char *oom_buffer_ptr = oom_buffer;
 
-static bool appendPQExpBufferVA(PQExpBuffer str, const char *fmt, va_list args) pg_attribute_printf(2, 0);
+static bool appendPQExpBufferVA_internal(PQExpBuffer str, const char *fmt, va_list args) pg_attribute_printf(2, 0);
 
 
 /*
@@ -250,7 +250,7 @@ printfPQExpBuffer(PQExpBuffer str, const char *fmt,...)
 	{
 		errno = save_errno;
 		va_start(args, fmt);
-		done = appendPQExpBufferVA(str, fmt, args);
+		done = appendPQExpBufferVA_internal(str, fmt, args);
 		va_end(args);
 	} while (!done);
 }
@@ -278,13 +278,30 @@ appendPQExpBuffer(PQExpBuffer str, const char *fmt,...)
 	{
 		errno = save_errno;
 		va_start(args, fmt);
-		done = appendPQExpBufferVA(str, fmt, args);
+		done = appendPQExpBufferVA_internal(str, fmt, args);
 		va_end(args);
 	} while (!done);
 }
 
+void
+appendPQExpBufferVA(PQExpBuffer str, const char *fmt, va_list args)
+{
+	int			save_errno = errno;
+	bool		done;
+
+	if (PQExpBufferBroken(str))
+		return;					/* already failed */
+
+	/* Loop in case we have to retry after enlarging the buffer. */
+	do
+	{
+		errno = save_errno;
+		done = appendPQExpBufferVA_internal(str, fmt, args);
+	} while (!done);
+}
+
 /*
- * appendPQExpBufferVA
+ * appendPQExpBufferVA_internal
  * Shared guts of printfPQExpBuffer/appendPQExpBuffer.
  * Attempt to format data and append it to str.  Returns true if done
  * (either successful or hard failure), false if need to retry.
@@ -293,7 +310,7 @@ appendPQExpBuffer(PQExpBuffer str, const char *fmt,...)
  * when looping, in case the fmt contains "%m".
  */
 static bool
-appendPQExpBufferVA(PQExpBuffer str, const char *fmt, va_list args)
+appendPQExpBufferVA_internal(PQExpBuffer str, const char *fmt, va_list args)
 {
 	size_t		avail;
 	size_t		needed;
diff --git a/src/interfaces/libpq/pqexpbuffer.h b/src/interfaces/libpq/pqexpbuffer.h
index efd652c80a..b8216b8a7b 100644
--- a/src/interfaces/libpq/pqexpbuffer.h
+++ b/src/interfaces/libpq/pqexpbuffer.h
@@ -157,6 +157,8 @@ extern void printfPQExpBuffer(PQExpBuffer str, const char *fmt,...) pg_attribute
  */
 extern void appendPQExpBuffer(PQExpBuffer str, const char *fmt,...) pg_attribute_printf(2, 3);
 
+extern void appendPQExpBufferVA(PQExpBuffer str, const char *fmt, va_list args) pg_attribute_printf(2, 0);
+
 /*------------------------
  * appendPQExpBufferStr
  * Append the given string to a PQExpBuffer, allocating more space
-- 
2.37.1

#2Andres Freund
andres@anarazel.de
In reply to: Peter Eisentraut (#1)
Re: libpq error message refactoring

Hi,

On 2022-08-25 16:34:26 +0200, Peter Eisentraut wrote:

libpq now contains a mix of error message strings that end with newlines and
don't end with newlines, due to some newer code paths with new ways of
passing errors around. This has now gotten me confused a few too many times
both during development and translation. So I looked into whether we can
unify this, similar to how we have done elsewhere (e.g., pg_upgrade). I
came up with the attached patch. It's not complete, but it shows the idea
and it looks like a nice simplification to me. Thoughts on this approach?

This patch has been failing for a while:
https://cirrus-ci.com/github/postgresql-cfbot/postgresql/commitfest/39/3854

Interestingly, previously the error only happened when targetting windows, but
meson also shows it on freebsd.

It's not the cause of this failure, I think, but doesn't appendPQExpBufferVA
need to be added to exports.txt?

Greetings,

Andres Freund

#3Peter Eisentraut
peter.eisentraut@enterprisedb.com
In reply to: Andres Freund (#2)
Re: libpq error message refactoring

On 22.09.22 17:42, Andres Freund wrote:

This patch has been failing for a while:
https://cirrus-ci.com/github/postgresql-cfbot/postgresql/commitfest/39/3854

Interestingly, previously the error only happened when targetting windows, but
meson also shows it on freebsd.

It's not the cause of this failure, I think, but doesn't appendPQExpBufferVA
need to be added to exports.txt?

I don't want to make that function available to users of libpq, just use
it inside libpq across .c files. Is there no visibility level for that?
Is that also the problem in the freebsd build?

#4Andres Freund
andres@anarazel.de
In reply to: Peter Eisentraut (#3)
Re: libpq error message refactoring

HHi,

On 2022-09-22 22:00:00 -0400, Peter Eisentraut wrote:

On 22.09.22 17:42, Andres Freund wrote:

This patch has been failing for a while:
https://cirrus-ci.com/github/postgresql-cfbot/postgresql/commitfest/39/3854

Interestingly, previously the error only happened when targetting windows, but
meson also shows it on freebsd.

It's not the cause of this failure, I think, but doesn't appendPQExpBufferVA
need to be added to exports.txt?

I don't want to make that function available to users of libpq, just use it
inside libpq across .c files. Is there no visibility level for that? Is
that also the problem in the freebsd build?

I suspect the appendPQExpBufferVA is orthogonal - most (all?) of the other
functions in pqexpbuffer.h are visible, so it feels weird/confusing to not
make appendPQExpBufferVA() available. I just noticed it when trying to
understand the linker failure - which I still don't...

Greetings,

Andres Freund

#5Tom Lane
tgl@sss.pgh.pa.us
In reply to: Peter Eisentraut (#3)
Re: libpq error message refactoring

Peter Eisentraut <peter.eisentraut@enterprisedb.com> writes:

On 22.09.22 17:42, Andres Freund wrote:

It's not the cause of this failure, I think, but doesn't appendPQExpBufferVA
need to be added to exports.txt?

I don't want to make that function available to users of libpq, just use
it inside libpq across .c files. Is there no visibility level for that?

Should "just work", I should think. I agree with Andres that that's
not the cause of the build failure. I wonder if somehow the failing
links are picking up the wrong libpq.a.

Separately from that: is it really okay to delegate use of a va_list
argument like that? The other call paths of
appendPQExpBufferVA[_internal] write va_start/va_end directly around it,
not somewhere else in the call chain. I'm too tired to language-lawyer
out what happens when you do it like this, but I'm suspecting that it's
not well-defined portable behavior.

I think what you probably need to do is export appendPQExpBufferVA
as-is and require libpq_append_error to provide the error loop.

regards, tom lane

#6Tom Lane
tgl@sss.pgh.pa.us
In reply to: Andres Freund (#4)
Re: libpq error message refactoring

Andres Freund <andres@anarazel.de> writes:

I suspect the appendPQExpBufferVA is orthogonal - most (all?) of the other
functions in pqexpbuffer.h are visible, so it feels weird/confusing to not
make appendPQExpBufferVA() available.

I thought the same to start with, but if I'm right in my nearby reply
that we'll have to make callers loop around appendPQExpBufferVA,
then it seems like a good idea to keep it closely held.

More than zero commentary about that would be a good thing, too.

regards, tom lane

#7Andres Freund
andres@anarazel.de
In reply to: Andres Freund (#4)
Re: libpq error message refactoring

Hi,

On 2022-09-22 19:27:27 -0700, Andres Freund wrote:

I just noticed it when trying to understand the linker failure - which I
still don't...

Heh, figured it out. It's inside #ifdef ENABLE_NLS. So it fails on all
platforms without NLS enabled.

Greetings,

Andres Freund

#8Tom Lane
tgl@sss.pgh.pa.us
In reply to: Andres Freund (#7)
Re: libpq error message refactoring

Andres Freund <andres@anarazel.de> writes:

Heh, figured it out. It's inside #ifdef ENABLE_NLS. So it fails on all
platforms without NLS enabled.

Argh, how simple!

The question about va_start/va_end placement still stands, though.

regards, tom lane

#9Peter Eisentraut
peter.eisentraut@enterprisedb.com
In reply to: Andres Freund (#7)
1 attachment(s)
Re: libpq error message refactoring

On 23.09.22 04:45, Andres Freund wrote:

On 2022-09-22 19:27:27 -0700, Andres Freund wrote:

I just noticed it when trying to understand the linker failure - which I
still don't...

Heh, figured it out. It's inside #ifdef ENABLE_NLS. So it fails on all
platforms without NLS enabled.

Hah!

Here is an updated patch to get the CI clean. I'll look into the other
discussed issues later.

Attachments:

v2-0001-WIP-libpq_append_error.patchtext/plain; charset=UTF-8; name=v2-0001-WIP-libpq_append_error.patchDownload
From de4cb33dcc98c7c71f21349eba650105a3385cbd Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter@eisentraut.org>
Date: Fri, 23 Sep 2022 16:26:16 -0400
Subject: [PATCH v2] WIP: libpq_append_error

Discussion: https://www.postgresql.org/message-id/flat/7c0232ef-7b44-68db-599d-b327d0640a77@enterprisedb.com
---
 src/interfaces/libpq/fe-connect.c  | 271 +++++++++++------------------
 src/interfaces/libpq/fe-exec.c     | 110 ++++--------
 src/interfaces/libpq/fe-misc.c     |  15 ++
 src/interfaces/libpq/libpq-int.h   |   2 +
 src/interfaces/libpq/nls.mk        |   4 +-
 src/interfaces/libpq/pqexpbuffer.c |  27 ++-
 src/interfaces/libpq/pqexpbuffer.h |   2 +
 7 files changed, 186 insertions(+), 245 deletions(-)

diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index 746e9b4f1efc..b99c4549d08e 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -896,8 +896,7 @@ fillPGconn(PGconn *conn, PQconninfoOption *connOptions)
 				*connmember = strdup(tmp);
 				if (*connmember == NULL)
 				{
-					appendPQExpBufferStr(&conn->errorMessage,
-										 libpq_gettext("out of memory\n"));
+					libpq_append_error(conn, "out of memory");
 					return false;
 				}
 			}
@@ -1079,9 +1078,8 @@ connectOptions2(PGconn *conn)
 		if (more || i != conn->nconnhost)
 		{
 			conn->status = CONNECTION_BAD;
-			appendPQExpBuffer(&conn->errorMessage,
-							  libpq_gettext("could not match %d host names to %d hostaddr values\n"),
-							  count_comma_separated_elems(conn->pghost), conn->nconnhost);
+			libpq_append_error(conn, "could not match %d host names to %d hostaddr values",
+							   count_comma_separated_elems(conn->pghost), conn->nconnhost);
 			return false;
 		}
 	}
@@ -1160,9 +1158,8 @@ connectOptions2(PGconn *conn)
 		else if (more || i != conn->nconnhost)
 		{
 			conn->status = CONNECTION_BAD;
-			appendPQExpBuffer(&conn->errorMessage,
-							  libpq_gettext("could not match %d port numbers to %d hosts\n"),
-							  count_comma_separated_elems(conn->pgport), conn->nconnhost);
+			libpq_append_error(conn, "could not match %d port numbers to %d hosts",
+							   count_comma_separated_elems(conn->pgport), conn->nconnhost);
 			return false;
 		}
 	}
@@ -1250,9 +1247,8 @@ connectOptions2(PGconn *conn)
 			&& strcmp(conn->channel_binding, "require") != 0)
 		{
 			conn->status = CONNECTION_BAD;
-			appendPQExpBuffer(&conn->errorMessage,
-							  libpq_gettext("invalid %s value: \"%s\"\n"),
-							  "channel_binding", conn->channel_binding);
+			libpq_append_error(conn, "invalid %s value: \"%s\"",
+							   "channel_binding", conn->channel_binding);
 			return false;
 		}
 	}
@@ -1276,9 +1272,8 @@ connectOptions2(PGconn *conn)
 			&& strcmp(conn->sslmode, "verify-full") != 0)
 		{
 			conn->status = CONNECTION_BAD;
-			appendPQExpBuffer(&conn->errorMessage,
-							  libpq_gettext("invalid %s value: \"%s\"\n"),
-							  "sslmode", conn->sslmode);
+			libpq_append_error(conn, "invalid %s value: \"%s\"",
+							   "sslmode", conn->sslmode);
 			return false;
 		}
 
@@ -1297,9 +1292,8 @@ connectOptions2(PGconn *conn)
 			case 'r':			/* "require" */
 			case 'v':			/* "verify-ca" or "verify-full" */
 				conn->status = CONNECTION_BAD;
-				appendPQExpBuffer(&conn->errorMessage,
-								  libpq_gettext("sslmode value \"%s\" invalid when SSL support is not compiled in\n"),
-								  conn->sslmode);
+				libpq_append_error(conn, "sslmode value \"%s\" invalid when SSL support is not compiled in",
+								   conn->sslmode);
 				return false;
 		}
 #endif
@@ -1318,19 +1312,17 @@ connectOptions2(PGconn *conn)
 	if (!sslVerifyProtocolVersion(conn->ssl_min_protocol_version))
 	{
 		conn->status = CONNECTION_BAD;
-		appendPQExpBuffer(&conn->errorMessage,
-						  libpq_gettext("invalid %s value: \"%s\"\n"),
-						  "ssl_min_protocol_version",
-						  conn->ssl_min_protocol_version);
+		libpq_append_error(conn, "invalid %s value: \"%s\"",
+						   "ssl_min_protocol_version",
+						   conn->ssl_min_protocol_version);
 		return false;
 	}
 	if (!sslVerifyProtocolVersion(conn->ssl_max_protocol_version))
 	{
 		conn->status = CONNECTION_BAD;
-		appendPQExpBuffer(&conn->errorMessage,
-						  libpq_gettext("invalid %s value: \"%s\"\n"),
-						  "ssl_max_protocol_version",
-						  conn->ssl_max_protocol_version);
+		libpq_append_error(conn, "invalid %s value: \"%s\"",
+						   "ssl_max_protocol_version",
+						   conn->ssl_max_protocol_version);
 		return false;
 	}
 
@@ -1345,8 +1337,7 @@ connectOptions2(PGconn *conn)
 								conn->ssl_max_protocol_version))
 	{
 		conn->status = CONNECTION_BAD;
-		appendPQExpBufferStr(&conn->errorMessage,
-							 libpq_gettext("invalid SSL protocol version range\n"));
+		libpq_append_error(conn, "invalid SSL protocol version range");
 		return false;
 	}
 
@@ -1360,19 +1351,15 @@ connectOptions2(PGconn *conn)
 			strcmp(conn->gssencmode, "require") != 0)
 		{
 			conn->status = CONNECTION_BAD;
-			appendPQExpBuffer(&conn->errorMessage,
-							  libpq_gettext("invalid %s value: \"%s\"\n"),
-							  "gssencmode",
-							  conn->gssencmode);
+			libpq_append_error(conn, "invalid %s value: \"%s\"", "gssencmode", conn->gssencmode);
 			return false;
 		}
 #ifndef ENABLE_GSS
 		if (strcmp(conn->gssencmode, "require") == 0)
 		{
 			conn->status = CONNECTION_BAD;
-			appendPQExpBuffer(&conn->errorMessage,
-							  libpq_gettext("gssencmode value \"%s\" invalid when GSSAPI support is not compiled in\n"),
-							  conn->gssencmode);
+			libpq_append_error(conn, "gssencmode value \"%s\" invalid when GSSAPI support is not compiled in",
+							   conn->gssencmode);
 			return false;
 		}
 #endif
@@ -1404,10 +1391,9 @@ connectOptions2(PGconn *conn)
 		else
 		{
 			conn->status = CONNECTION_BAD;
-			appendPQExpBuffer(&conn->errorMessage,
-							  libpq_gettext("invalid %s value: \"%s\"\n"),
-							  "target_session_attrs",
-							  conn->target_session_attrs);
+			libpq_append_error(conn, "invalid %s value: \"%s\"",
+							   "target_session_attrs",
+							   conn->target_session_attrs);
 			return false;
 		}
 	}
@@ -1437,8 +1423,7 @@ connectOptions2(PGconn *conn)
 
 oom_error:
 	conn->status = CONNECTION_BAD;
-	appendPQExpBufferStr(&conn->errorMessage,
-						 libpq_gettext("out of memory\n"));
+	libpq_append_error(conn, "out of memory");
 	return false;
 }
 
@@ -1600,8 +1585,7 @@ PQsetdbLogin(const char *pghost, const char *pgport, const char *pgoptions,
 
 oom_error:
 	conn->status = CONNECTION_BAD;
-	appendPQExpBufferStr(&conn->errorMessage,
-						 libpq_gettext("out of memory\n"));
+	libpq_append_error(conn, "out of memory");
 	return conn;
 }
 
@@ -1624,9 +1608,8 @@ connectNoDelay(PGconn *conn)
 	{
 		char		sebuf[PG_STRERROR_R_BUFLEN];
 
-		appendPQExpBuffer(&conn->errorMessage,
-						  libpq_gettext("could not set socket to TCP no delay mode: %s\n"),
-						  SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof(sebuf)));
+		libpq_append_error(conn, "could not set socket to TCP no delay mode: %s",
+						   SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof(sebuf)));
 		return 0;
 	}
 #endif
@@ -1738,11 +1721,9 @@ connectFailureMessage(PGconn *conn, int errorno)
 					  SOCK_STRERROR(errorno, sebuf, sizeof(sebuf)));
 
 	if (conn->raddr.addr.ss_family == AF_UNIX)
-		appendPQExpBufferStr(&conn->errorMessage,
-							 libpq_gettext("\tIs the server running locally and accepting connections on that socket?\n"));
+		libpq_append_error(conn, "\tIs the server running locally and accepting connections on that socket?");
 	else
-		appendPQExpBufferStr(&conn->errorMessage,
-							 libpq_gettext("\tIs the server running on that host and accepting TCP/IP connections?\n"));
+		libpq_append_error(conn, "\tIs the server running on that host and accepting TCP/IP connections?");
 }
 
 /*
@@ -1805,9 +1786,8 @@ parse_int_param(const char *value, int *result, PGconn *conn,
 	return true;
 
 error:
-	appendPQExpBuffer(&conn->errorMessage,
-					  libpq_gettext("invalid integer value \"%s\" for connection option \"%s\"\n"),
-					  value, context);
+	libpq_append_error(conn, "invalid integer value \"%s\" for connection option \"%s\"",
+					   value, context);
 	return false;
 }
 
@@ -1835,11 +1815,10 @@ setKeepalivesIdle(PGconn *conn)
 	{
 		char		sebuf[PG_STRERROR_R_BUFLEN];
 
-		appendPQExpBuffer(&conn->errorMessage,
-						  libpq_gettext("%s(%s) failed: %s\n"),
-						  "setsockopt",
-						  PG_TCP_KEEPALIVE_IDLE_STR,
-						  SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof(sebuf)));
+		libpq_append_error(conn, "%s(%s) failed: %s",
+						   "setsockopt",
+						   PG_TCP_KEEPALIVE_IDLE_STR,
+						   SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof(sebuf)));
 		return 0;
 	}
 #endif
@@ -1870,11 +1849,10 @@ setKeepalivesInterval(PGconn *conn)
 	{
 		char		sebuf[PG_STRERROR_R_BUFLEN];
 
-		appendPQExpBuffer(&conn->errorMessage,
-						  libpq_gettext("%s(%s) failed: %s\n"),
-						  "setsockopt",
-						  "TCP_KEEPINTVL",
-						  SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof(sebuf)));
+		libpq_append_error(conn, "%s(%s) failed: %s",
+						   "setsockopt",
+						   "TCP_KEEPINTVL",
+						   SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof(sebuf)));
 		return 0;
 	}
 #endif
@@ -1906,11 +1884,10 @@ setKeepalivesCount(PGconn *conn)
 	{
 		char		sebuf[PG_STRERROR_R_BUFLEN];
 
-		appendPQExpBuffer(&conn->errorMessage,
-						  libpq_gettext("%s(%s) failed: %s\n"),
-						  "setsockopt",
-						  "TCP_KEEPCNT",
-						  SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof(sebuf)));
+		libpq_append_error(conn, "%s(%s) failed: %s",
+						   "setsockopt",
+						   "TCP_KEEPCNT",
+						   SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof(sebuf)));
 		return 0;
 	}
 #endif
@@ -1971,8 +1948,7 @@ prepKeepalivesWin32(PGconn *conn)
 
 	if (!setKeepalivesWin32(conn->sock, idle, interval))
 	{
-		appendPQExpBuffer(&conn->errorMessage,
-						  libpq_gettext("%s(%s) failed: error code %d\n"),
+		libpq_append_error(conn, "%s(%s) failed: error code %d",
 						  "WSAIoctl", "SIO_KEEPALIVE_VALS",
 						  WSAGetLastError());
 		return 0;
@@ -2006,11 +1982,10 @@ setTCPUserTimeout(PGconn *conn)
 	{
 		char		sebuf[256];
 
-		appendPQExpBuffer(&conn->errorMessage,
-						  libpq_gettext("%s(%s) failed: %s\n"),
-						  "setsockopt",
-						  "TCP_USER_TIMEOUT",
-						  SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof(sebuf)));
+		libpq_append_error(conn, "%s(%s) failed: %s",
+						   "setsockopt",
+						   "TCP_USER_TIMEOUT",
+						   SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof(sebuf)));
 		return 0;
 	}
 #endif
@@ -2286,8 +2261,7 @@ PQconnectPoll(PGconn *conn)
 			break;
 
 		default:
-			appendPQExpBufferStr(&conn->errorMessage,
-								 libpq_gettext("invalid connection state, probably indicative of memory corruption\n"));
+			libpq_append_error(conn, "invalid connection state, probably indicative of memory corruption");
 			goto error_return;
 	}
 
@@ -2365,9 +2339,7 @@ PQconnectPoll(PGconn *conn)
 
 			if (thisport < 1 || thisport > 65535)
 			{
-				appendPQExpBuffer(&conn->errorMessage,
-								  libpq_gettext("invalid port number: \"%s\"\n"),
-								  ch->port);
+				libpq_append_error(conn, "invalid port number: \"%s\"", ch->port);
 				goto keep_going;
 			}
 		}
@@ -2381,9 +2353,8 @@ PQconnectPoll(PGconn *conn)
 										 &conn->addrlist);
 				if (ret || !conn->addrlist)
 				{
-					appendPQExpBuffer(&conn->errorMessage,
-									  libpq_gettext("could not translate host name \"%s\" to address: %s\n"),
-									  ch->host, gai_strerror(ret));
+					libpq_append_error(conn, "could not translate host name \"%s\" to address: %s",
+									   ch->host, gai_strerror(ret));
 					goto keep_going;
 				}
 				break;
@@ -2394,9 +2365,8 @@ PQconnectPoll(PGconn *conn)
 										 &conn->addrlist);
 				if (ret || !conn->addrlist)
 				{
-					appendPQExpBuffer(&conn->errorMessage,
-									  libpq_gettext("could not parse network address \"%s\": %s\n"),
-									  ch->hostaddr, gai_strerror(ret));
+					libpq_append_error(conn, "could not parse network address \"%s\": %s",
+									   ch->hostaddr, gai_strerror(ret));
 					goto keep_going;
 				}
 				break;
@@ -2406,10 +2376,9 @@ PQconnectPoll(PGconn *conn)
 				UNIXSOCK_PATH(portstr, thisport, ch->host);
 				if (strlen(portstr) >= UNIXSOCK_PATH_BUFLEN)
 				{
-					appendPQExpBuffer(&conn->errorMessage,
-									  libpq_gettext("Unix-domain socket path \"%s\" is too long (maximum %d bytes)\n"),
-									  portstr,
-									  (int) (UNIXSOCK_PATH_BUFLEN - 1));
+					libpq_append_error(conn, "Unix-domain socket path \"%s\" is too long (maximum %d bytes)",
+									   portstr,
+									   (int) (UNIXSOCK_PATH_BUFLEN - 1));
 					goto keep_going;
 				}
 
@@ -2421,9 +2390,8 @@ PQconnectPoll(PGconn *conn)
 										 &conn->addrlist);
 				if (ret || !conn->addrlist)
 				{
-					appendPQExpBuffer(&conn->errorMessage,
-									  libpq_gettext("could not translate Unix-domain socket path \"%s\" to address: %s\n"),
-									  portstr, gai_strerror(ret));
+					libpq_append_error(conn, "could not translate Unix-domain socket path \"%s\" to address: %s",
+									   portstr, gai_strerror(ret));
 					goto keep_going;
 				}
 				break;
@@ -2544,9 +2512,8 @@ PQconnectPoll(PGconn *conn)
 							goto keep_going;
 						}
 						emitHostIdentityInfo(conn, host_addr);
-						appendPQExpBuffer(&conn->errorMessage,
-										  libpq_gettext("could not create socket: %s\n"),
-										  SOCK_STRERROR(errorno, sebuf, sizeof(sebuf)));
+						libpq_append_error(conn, "could not create socket: %s",
+										   SOCK_STRERROR(errorno, sebuf, sizeof(sebuf)));
 						goto error_return;
 					}
 
@@ -2575,9 +2542,8 @@ PQconnectPoll(PGconn *conn)
 					}
 					if (!pg_set_noblock(conn->sock))
 					{
-						appendPQExpBuffer(&conn->errorMessage,
-										  libpq_gettext("could not set socket to nonblocking mode: %s\n"),
-										  SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof(sebuf)));
+						libpq_append_error(conn, "could not set socket to nonblocking mode: %s",
+										   SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof(sebuf)));
 						conn->try_next_addr = true;
 						goto keep_going;
 					}
@@ -2585,9 +2551,8 @@ PQconnectPoll(PGconn *conn)
 #ifdef F_SETFD
 					if (fcntl(conn->sock, F_SETFD, FD_CLOEXEC) == -1)
 					{
-						appendPQExpBuffer(&conn->errorMessage,
-										  libpq_gettext("could not set socket to close-on-exec mode: %s\n"),
-										  SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof(sebuf)));
+						libpq_append_error(conn, "could not set socket to close-on-exec mode: %s",
+										   SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof(sebuf)));
 						conn->try_next_addr = true;
 						goto keep_going;
 					}
@@ -2603,8 +2568,7 @@ PQconnectPoll(PGconn *conn)
 
 						if (usekeepalives < 0)
 						{
-							appendPQExpBufferStr(&conn->errorMessage,
-												 libpq_gettext("keepalives parameter must be an integer\n"));
+							libpq_append_error(conn, "keepalives parameter must be an integer");
 							err = 1;
 						}
 						else if (usekeepalives == 0)
@@ -2616,11 +2580,10 @@ PQconnectPoll(PGconn *conn)
 											SOL_SOCKET, SO_KEEPALIVE,
 											(char *) &on, sizeof(on)) < 0)
 						{
-							appendPQExpBuffer(&conn->errorMessage,
-											  libpq_gettext("%s(%s) failed: %s\n"),
-											  "setsockopt",
-											  "SO_KEEPALIVE",
-											  SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof(sebuf)));
+							libpq_append_error(conn, "%s(%s) failed: %s",
+											   "setsockopt",
+											   "SO_KEEPALIVE",
+											   SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof(sebuf)));
 							err = 1;
 						}
 						else if (!setKeepalivesIdle(conn)
@@ -2744,9 +2707,8 @@ PQconnectPoll(PGconn *conn)
 				if (getsockopt(conn->sock, SOL_SOCKET, SO_ERROR,
 							   (char *) &optval, &optlen) == -1)
 				{
-					appendPQExpBuffer(&conn->errorMessage,
-									  libpq_gettext("could not get socket error status: %s\n"),
-									  SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof(sebuf)));
+					libpq_append_error(conn, "could not get socket error status: %s",
+									   SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof(sebuf)));
 					goto error_return;
 				}
 				else if (optval != 0)
@@ -2772,9 +2734,8 @@ PQconnectPoll(PGconn *conn)
 								(struct sockaddr *) &conn->laddr.addr,
 								&conn->laddr.salen) < 0)
 				{
-					appendPQExpBuffer(&conn->errorMessage,
-									  libpq_gettext("could not get client address from socket: %s\n"),
-									  SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof(sebuf)));
+					libpq_append_error(conn, "could not get client address from socket: %s",
+									   SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof(sebuf)));
 					goto error_return;
 				}
 
@@ -2811,12 +2772,10 @@ PQconnectPoll(PGconn *conn)
 						 * stub
 						 */
 						if (errno == ENOSYS)
-							appendPQExpBufferStr(&conn->errorMessage,
-												 libpq_gettext("requirepeer parameter is not supported on this platform\n"));
+							libpq_append_error(conn, "requirepeer parameter is not supported on this platform");
 						else
-							appendPQExpBuffer(&conn->errorMessage,
-											  libpq_gettext("could not get peer credentials: %s\n"),
-											  strerror_r(errno, sebuf, sizeof(sebuf)));
+							libpq_append_error(conn, "could not get peer credentials: %s",
+											   strerror_r(errno, sebuf, sizeof(sebuf)));
 						goto error_return;
 					}
 
@@ -2828,9 +2787,8 @@ PQconnectPoll(PGconn *conn)
 
 					if (strcmp(remote_username, conn->requirepeer) != 0)
 					{
-						appendPQExpBuffer(&conn->errorMessage,
-										  libpq_gettext("requirepeer specifies \"%s\", but actual peer user name is \"%s\"\n"),
-										  conn->requirepeer, remote_username);
+						libpq_append_error(conn, "requirepeer specifies \"%s\", but actual peer user name is \"%s\"",
+										   conn->requirepeer, remote_username);
 						free(remote_username);
 						goto error_return;
 					}
@@ -2870,9 +2828,8 @@ PQconnectPoll(PGconn *conn)
 
 					if (pqPacketSend(conn, 0, &pv, sizeof(pv)) != STATUS_OK)
 					{
-						appendPQExpBuffer(&conn->errorMessage,
-										  libpq_gettext("could not send GSSAPI negotiation packet: %s\n"),
-										  SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof(sebuf)));
+						libpq_append_error(conn, "could not send GSSAPI negotiation packet: %s",
+										   SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof(sebuf)));
 						goto error_return;
 					}
 
@@ -2882,8 +2839,8 @@ PQconnectPoll(PGconn *conn)
 				}
 				else if (!conn->gctx && conn->gssencmode[0] == 'r')
 				{
-					appendPQExpBufferStr(&conn->errorMessage,
-										 libpq_gettext("GSSAPI encryption required but was impossible (possibly no credential cache, no server support, or using a local socket)\n"));
+					libpq_append_error(conn,
+									   "GSSAPI encryption required but was impossible (possibly no credential cache, no server support, or using a local socket)");
 					goto error_return;
 				}
 #endif
@@ -2924,9 +2881,8 @@ PQconnectPoll(PGconn *conn)
 					pv = pg_hton32(NEGOTIATE_SSL_CODE);
 					if (pqPacketSend(conn, 0, &pv, sizeof(pv)) != STATUS_OK)
 					{
-						appendPQExpBuffer(&conn->errorMessage,
-										  libpq_gettext("could not send SSL negotiation packet: %s\n"),
-										  SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof(sebuf)));
+						libpq_append_error(conn, "could not send SSL negotiation packet: %s",
+										   SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof(sebuf)));
 						goto error_return;
 					}
 					/* Ok, wait for response */
@@ -2942,8 +2898,7 @@ PQconnectPoll(PGconn *conn)
 													EnvironmentOptions);
 				if (!startpacket)
 				{
-					appendPQExpBufferStr(&conn->errorMessage,
-										 libpq_gettext("out of memory\n"));
+					libpq_append_error(conn, "out of memory");
 					goto error_return;
 				}
 
@@ -2955,9 +2910,8 @@ PQconnectPoll(PGconn *conn)
 				 */
 				if (pqPacketSend(conn, 0, startpacket, packetlen) != STATUS_OK)
 				{
-					appendPQExpBuffer(&conn->errorMessage,
-									  libpq_gettext("could not send startup packet: %s\n"),
-									  SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof(sebuf)));
+					libpq_append_error(conn, "could not send startup packet: %s",
+									   SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof(sebuf)));
 					free(startpacket);
 					goto error_return;
 				}
@@ -3031,8 +2985,7 @@ PQconnectPoll(PGconn *conn)
 														 * "verify-full" */
 						{
 							/* Require SSL, but server does not want it */
-							appendPQExpBufferStr(&conn->errorMessage,
-												 libpq_gettext("server does not support SSL, but SSL was required\n"));
+							libpq_append_error(conn, "server does not support SSL, but SSL was required");
 							goto error_return;
 						}
 						/* Otherwise, proceed with normal startup */
@@ -3058,9 +3011,8 @@ PQconnectPoll(PGconn *conn)
 					}
 					else
 					{
-						appendPQExpBuffer(&conn->errorMessage,
-										  libpq_gettext("received invalid response to SSL negotiation: %c\n"),
-										  SSLok);
+						libpq_append_error(conn, "received invalid response to SSL negotiation: %c",
+										   SSLok);
 						goto error_return;
 					}
 				}
@@ -3079,8 +3031,7 @@ PQconnectPoll(PGconn *conn)
 					 */
 					if (conn->inCursor != conn->inEnd)
 					{
-						appendPQExpBufferStr(&conn->errorMessage,
-											 libpq_gettext("received unencrypted data after SSL response\n"));
+						libpq_append_error(conn, "received unencrypted data after SSL response");
 						goto error_return;
 					}
 
@@ -3160,8 +3111,7 @@ PQconnectPoll(PGconn *conn)
 						/* Server doesn't want GSSAPI; fall back if we can */
 						if (conn->gssencmode[0] == 'r')
 						{
-							appendPQExpBufferStr(&conn->errorMessage,
-												 libpq_gettext("server doesn't support GSSAPI encryption, but it was required\n"));
+							libpq_append_error(conn, "server doesn't support GSSAPI encryption, but it was required");
 							goto error_return;
 						}
 
@@ -3172,9 +3122,8 @@ PQconnectPoll(PGconn *conn)
 					}
 					else if (gss_ok != 'G')
 					{
-						appendPQExpBuffer(&conn->errorMessage,
-										  libpq_gettext("received invalid response to GSSAPI negotiation: %c\n"),
-										  gss_ok);
+						libpq_append_error(conn, "received invalid response to GSSAPI negotiation: %c",
+										   gss_ok);
 						goto error_return;
 					}
 				}
@@ -3191,8 +3140,7 @@ PQconnectPoll(PGconn *conn)
 					 */
 					if (conn->inCursor != conn->inEnd)
 					{
-						appendPQExpBufferStr(&conn->errorMessage,
-											 libpq_gettext("received unencrypted data after GSSAPI encryption response\n"));
+						libpq_append_error(conn, "received unencrypted data after GSSAPI encryption response");
 						goto error_return;
 					}
 
@@ -3251,9 +3199,8 @@ PQconnectPoll(PGconn *conn)
 				 */
 				if (!(beresp == 'R' || beresp == 'E'))
 				{
-					appendPQExpBuffer(&conn->errorMessage,
-									  libpq_gettext("expected authentication request from server, but received %c\n"),
-									  beresp);
+					libpq_append_error(conn, "expected authentication request from server, but received %c",
+									   beresp);
 					goto error_return;
 				}
 
@@ -3276,9 +3223,8 @@ PQconnectPoll(PGconn *conn)
 				 */
 				if (beresp == 'R' && (msgLength < 8 || msgLength > 2000))
 				{
-					appendPQExpBuffer(&conn->errorMessage,
-									  libpq_gettext("expected authentication request from server, but received %c\n"),
-									  beresp);
+					libpq_append_error(conn, "expected authentication request from server, but received %c",
+									   beresp);
 					goto error_return;
 				}
 
@@ -3483,8 +3429,7 @@ PQconnectPoll(PGconn *conn)
 				if (res)
 				{
 					if (res->resultStatus != PGRES_FATAL_ERROR)
-						appendPQExpBufferStr(&conn->errorMessage,
-											 libpq_gettext("unexpected message from server during startup\n"));
+						libpq_append_error(conn, "unexpected message from server during startup");
 					else if (conn->send_appname &&
 							 (conn->appname || conn->fbappname))
 					{
@@ -3575,11 +3520,9 @@ PQconnectPoll(PGconn *conn)
 					{
 						/* Wrong server state, reject and try the next host */
 						if (conn->target_server_type == SERVER_TYPE_READ_WRITE)
-							appendPQExpBufferStr(&conn->errorMessage,
-												 libpq_gettext("session is read-only\n"));
+							libpq_append_error(conn, "session is read-only");
 						else
-							appendPQExpBufferStr(&conn->errorMessage,
-												 libpq_gettext("session is not read-only\n"));
+							libpq_append_error(conn, "session is not read-only");
 
 						/* Close connection politely. */
 						conn->status = CONNECTION_OK;
@@ -3632,11 +3575,9 @@ PQconnectPoll(PGconn *conn)
 					{
 						/* Wrong server state, reject and try the next host */
 						if (conn->target_server_type == SERVER_TYPE_PRIMARY)
-							appendPQExpBufferStr(&conn->errorMessage,
-												 libpq_gettext("server is in hot standby mode\n"));
+							libpq_append_error(conn, "server is in hot standby mode");
 						else
-							appendPQExpBufferStr(&conn->errorMessage,
-												 libpq_gettext("server is not in hot standby mode\n"));
+							libpq_append_error(conn, "server is not in hot standby mode");
 
 						/* Close connection politely. */
 						conn->status = CONNECTION_OK;
diff --git a/src/interfaces/libpq/fe-exec.c b/src/interfaces/libpq/fe-exec.c
index 0274c1b156c6..5bc5d963aa68 100644
--- a/src/interfaces/libpq/fe-exec.c
+++ b/src/interfaces/libpq/fe-exec.c
@@ -1316,8 +1316,7 @@ pqAllocCmdQueueEntry(PGconn *conn)
 		entry = (PGcmdQueueEntry *) malloc(sizeof(PGcmdQueueEntry));
 		if (entry == NULL)
 		{
-			appendPQExpBufferStr(&conn->errorMessage,
-								 libpq_gettext("out of memory\n"));
+			libpq_append_error(conn, "out of memory");
 			return NULL;
 		}
 	}
@@ -1440,8 +1439,7 @@ PQsendQueryInternal(PGconn *conn, const char *query, bool newQuery)
 	/* check the argument */
 	if (!query)
 	{
-		appendPQExpBufferStr(&conn->errorMessage,
-							 libpq_gettext("command string is a null pointer\n"));
+		libpq_append_error(conn, "command string is a null pointer");
 		return 0;
 	}
 
@@ -1511,15 +1509,13 @@ PQsendQueryParams(PGconn *conn,
 	/* check the arguments */
 	if (!command)
 	{
-		appendPQExpBufferStr(&conn->errorMessage,
-							 libpq_gettext("command string is a null pointer\n"));
+		libpq_append_error(conn, "command string is a null pointer");
 		return 0;
 	}
 	if (nParams < 0 || nParams > PQ_QUERY_PARAM_MAX_LIMIT)
 	{
-		appendPQExpBuffer(&conn->errorMessage,
-						  libpq_gettext("number of parameters must be between 0 and %d\n"),
-						  PQ_QUERY_PARAM_MAX_LIMIT);
+		libpq_append_error(conn, "number of parameters must be between 0 and %d",
+						   PQ_QUERY_PARAM_MAX_LIMIT);
 		return 0;
 	}
 
@@ -1554,21 +1550,18 @@ PQsendPrepare(PGconn *conn,
 	/* check the arguments */
 	if (!stmtName)
 	{
-		appendPQExpBufferStr(&conn->errorMessage,
-							 libpq_gettext("statement name is a null pointer\n"));
+		libpq_append_error(conn, "statement name is a null pointer");
 		return 0;
 	}
 	if (!query)
 	{
-		appendPQExpBufferStr(&conn->errorMessage,
-							 libpq_gettext("command string is a null pointer\n"));
+		libpq_append_error(conn, "command string is a null pointer");
 		return 0;
 	}
 	if (nParams < 0 || nParams > PQ_QUERY_PARAM_MAX_LIMIT)
 	{
-		appendPQExpBuffer(&conn->errorMessage,
-						  libpq_gettext("number of parameters must be between 0 and %d\n"),
-						  PQ_QUERY_PARAM_MAX_LIMIT);
+		libpq_append_error(conn, "number of parameters must be between 0 and %d",
+						   PQ_QUERY_PARAM_MAX_LIMIT);
 		return 0;
 	}
 
@@ -1656,15 +1649,13 @@ PQsendQueryPrepared(PGconn *conn,
 	/* check the arguments */
 	if (!stmtName)
 	{
-		appendPQExpBufferStr(&conn->errorMessage,
-							 libpq_gettext("statement name is a null pointer\n"));
+		libpq_append_error(conn, "statement name is a null pointer");
 		return 0;
 	}
 	if (nParams < 0 || nParams > PQ_QUERY_PARAM_MAX_LIMIT)
 	{
-		appendPQExpBuffer(&conn->errorMessage,
-						  libpq_gettext("number of parameters must be between 0 and %d\n"),
-						  PQ_QUERY_PARAM_MAX_LIMIT);
+		libpq_append_error(conn, "number of parameters must be between 0 and %d",
+						   PQ_QUERY_PARAM_MAX_LIMIT);
 		return 0;
 	}
 
@@ -1700,8 +1691,7 @@ PQsendQueryStart(PGconn *conn, bool newQuery)
 	/* Don't try to send if we know there's no live connection. */
 	if (conn->status != CONNECTION_OK)
 	{
-		appendPQExpBufferStr(&conn->errorMessage,
-							 libpq_gettext("no connection to the server\n"));
+		libpq_append_error(conn, "no connection to the server");
 		return false;
 	}
 
@@ -1709,8 +1699,7 @@ PQsendQueryStart(PGconn *conn, bool newQuery)
 	if (conn->asyncStatus != PGASYNC_IDLE &&
 		conn->pipelineStatus == PQ_PIPELINE_OFF)
 	{
-		appendPQExpBufferStr(&conn->errorMessage,
-							 libpq_gettext("another command is already in progress\n"));
+		libpq_append_error(conn, "another command is already in progress");
 		return false;
 	}
 
@@ -1740,8 +1729,7 @@ PQsendQueryStart(PGconn *conn, bool newQuery)
 			case PGASYNC_COPY_IN:
 			case PGASYNC_COPY_OUT:
 			case PGASYNC_COPY_BOTH:
-				appendPQExpBufferStr(&conn->errorMessage,
-									 libpq_gettext("cannot queue commands during COPY\n"));
+				libpq_append_error(conn, "cannot queue commands during COPY");
 				return false;
 		}
 	}
@@ -1858,8 +1846,7 @@ PQsendQueryGuts(PGconn *conn,
 					nbytes = paramLengths[i];
 				else
 				{
-					appendPQExpBufferStr(&conn->errorMessage,
-										 libpq_gettext("length must be given for binary parameter\n"));
+					libpq_append_error(conn, "length must be given for binary parameter");
 					goto sendFailed;
 				}
 			}
@@ -2181,9 +2168,7 @@ PQgetResult(PGconn *conn)
 			res = getCopyResult(conn, PGRES_COPY_BOTH);
 			break;
 		default:
-			appendPQExpBuffer(&conn->errorMessage,
-							  libpq_gettext("unexpected asyncStatus: %d\n"),
-							  (int) conn->asyncStatus);
+			libpq_append_error(conn, "unexpected asyncStatus: %d", (int) conn->asyncStatus);
 			pqSaveErrorResult(conn);
 			conn->asyncStatus = PGASYNC_IDLE;	/* try to restore valid state */
 			res = pqPrepareAsyncResult(conn);
@@ -2339,8 +2324,7 @@ PQexecStart(PGconn *conn)
 
 	if (conn->pipelineStatus != PQ_PIPELINE_OFF)
 	{
-		appendPQExpBufferStr(&conn->errorMessage,
-							 libpq_gettext("synchronous command execution functions are not allowed in pipeline mode\n"));
+		libpq_append_error(conn, "synchronous command execution functions are not allowed in pipeline mode");
 		return false;
 	}
 
@@ -2373,8 +2357,7 @@ PQexecStart(PGconn *conn)
 		else if (resultStatus == PGRES_COPY_BOTH)
 		{
 			/* We don't allow PQexec during COPY BOTH */
-			appendPQExpBufferStr(&conn->errorMessage,
-								 libpq_gettext("PQexec not allowed during COPY BOTH\n"));
+			libpq_append_error(conn, "PQexec not allowed during COPY BOTH");
 			return false;
 		}
 		/* check for loss of connection, too */
@@ -2600,8 +2583,7 @@ PQputCopyData(PGconn *conn, const char *buffer, int nbytes)
 	if (conn->asyncStatus != PGASYNC_COPY_IN &&
 		conn->asyncStatus != PGASYNC_COPY_BOTH)
 	{
-		appendPQExpBufferStr(&conn->errorMessage,
-							 libpq_gettext("no COPY in progress\n"));
+		libpq_append_error(conn, "no COPY in progress");
 		return -1;
 	}
 
@@ -2656,8 +2638,7 @@ PQputCopyEnd(PGconn *conn, const char *errormsg)
 	if (conn->asyncStatus != PGASYNC_COPY_IN &&
 		conn->asyncStatus != PGASYNC_COPY_BOTH)
 	{
-		appendPQExpBufferStr(&conn->errorMessage,
-							 libpq_gettext("no COPY in progress\n"));
+		libpq_append_error(conn, "no COPY in progress");
 		return -1;
 	}
 
@@ -2725,8 +2706,7 @@ PQgetCopyData(PGconn *conn, char **buffer, int async)
 	if (conn->asyncStatus != PGASYNC_COPY_OUT &&
 		conn->asyncStatus != PGASYNC_COPY_BOTH)
 	{
-		appendPQExpBufferStr(&conn->errorMessage,
-							 libpq_gettext("no COPY in progress\n"));
+		libpq_append_error(conn, "no COPY in progress");
 		return -2;
 	}
 	return pqGetCopyData3(conn, buffer, async);
@@ -2905,17 +2885,14 @@ PQfn(PGconn *conn,
 
 	if (conn->pipelineStatus != PQ_PIPELINE_OFF)
 	{
-		appendPQExpBuffer(&conn->errorMessage,
-						  libpq_gettext("%s not allowed in pipeline mode\n"),
-						  "PQfn");
+		libpq_append_error(conn, "%s not allowed in pipeline mode", "PQfn");
 		return NULL;
 	}
 
 	if (conn->sock == PGINVALID_SOCKET || conn->asyncStatus != PGASYNC_IDLE ||
 		pgHavePendingResult(conn))
 	{
-		appendPQExpBufferStr(&conn->errorMessage,
-							 libpq_gettext("connection in wrong state\n"));
+		libpq_append_error(conn, "connection in wrong state");
 		return NULL;
 	}
 
@@ -2958,8 +2935,7 @@ PQenterPipelineMode(PGconn *conn)
 
 	if (conn->asyncStatus != PGASYNC_IDLE)
 	{
-		appendPQExpBufferStr(&conn->errorMessage,
-							 libpq_gettext("cannot enter pipeline mode, connection not idle\n"));
+		libpq_append_error(conn, "cannot enter pipeline mode, connection not idle");
 		return 0;
 	}
 
@@ -2995,13 +2971,11 @@ PQexitPipelineMode(PGconn *conn)
 		case PGASYNC_READY:
 		case PGASYNC_READY_MORE:
 			/* there are some uncollected results */
-			appendPQExpBufferStr(&conn->errorMessage,
-								 libpq_gettext("cannot exit pipeline mode with uncollected results\n"));
+			libpq_append_error(conn, "cannot exit pipeline mode with uncollected results");
 			return 0;
 
 		case PGASYNC_BUSY:
-			appendPQExpBufferStr(&conn->errorMessage,
-								 libpq_gettext("cannot exit pipeline mode while busy\n"));
+			libpq_append_error(conn, "cannot exit pipeline mode while busy");
 			return 0;
 
 		case PGASYNC_IDLE:
@@ -3012,15 +2986,13 @@ PQexitPipelineMode(PGconn *conn)
 		case PGASYNC_COPY_IN:
 		case PGASYNC_COPY_OUT:
 		case PGASYNC_COPY_BOTH:
-			appendPQExpBufferStr(&conn->errorMessage,
-								 libpq_gettext("cannot exit pipeline mode while in COPY\n"));
+			libpq_append_error(conn, "cannot exit pipeline mode while in COPY");
 	}
 
 	/* still work to process */
 	if (conn->cmd_queue_head != NULL)
 	{
-		appendPQExpBufferStr(&conn->errorMessage,
-							 libpq_gettext("cannot exit pipeline mode with uncollected results\n"));
+		libpq_append_error(conn, "cannot exit pipeline mode with uncollected results");
 		return 0;
 	}
 
@@ -3135,8 +3107,7 @@ pqPipelineProcessQueue(PGconn *conn)
 		conn->result = PQmakeEmptyPGresult(conn, PGRES_PIPELINE_ABORTED);
 		if (!conn->result)
 		{
-			appendPQExpBufferStr(&conn->errorMessage,
-								 libpq_gettext("out of memory\n"));
+			libpq_append_error(conn, "out of memory");
 			pqSaveErrorResult(conn);
 			return;
 		}
@@ -3179,8 +3150,7 @@ PQpipelineSync(PGconn *conn)
 
 	if (conn->pipelineStatus == PQ_PIPELINE_OFF)
 	{
-		appendPQExpBufferStr(&conn->errorMessage,
-							 libpq_gettext("cannot send pipeline when not in pipeline mode\n"));
+		libpq_append_error(conn, "cannot send pipeline when not in pipeline mode");
 		return 0;
 	}
 
@@ -3246,8 +3216,7 @@ PQsendFlushRequest(PGconn *conn)
 	/* Don't try to send if we know there's no live connection. */
 	if (conn->status != CONNECTION_OK)
 	{
-		appendPQExpBufferStr(&conn->errorMessage,
-							 libpq_gettext("no connection to the server\n"));
+		libpq_append_error(conn, "no connection to the server");
 		return 0;
 	}
 
@@ -3255,8 +3224,7 @@ PQsendFlushRequest(PGconn *conn)
 	if (conn->asyncStatus != PGASYNC_IDLE &&
 		conn->pipelineStatus == PQ_PIPELINE_OFF)
 	{
-		appendPQExpBufferStr(&conn->errorMessage,
-							 libpq_gettext("another command is already in progress\n"));
+		libpq_append_error(conn, "another command is already in progress");
 		return 0;
 	}
 
@@ -3992,8 +3960,7 @@ PQescapeStringInternal(PGconn *conn,
 			if (error)
 				*error = 1;
 			if (conn)
-				appendPQExpBufferStr(&conn->errorMessage,
-									 libpq_gettext("incomplete multibyte character\n"));
+				libpq_append_error(conn, "incomplete multibyte character");
 			for (; i < len; i++)
 			{
 				if (((size_t) (target - to)) / 2 >= length)
@@ -4083,8 +4050,7 @@ PQescapeInternal(PGconn *conn, const char *str, size_t len, bool as_ident)
 			/* Multibyte character overruns allowable length. */
 			if ((s - str) + charlen > len || memchr(s, 0, charlen) != NULL)
 			{
-				appendPQExpBufferStr(&conn->errorMessage,
-									 libpq_gettext("incomplete multibyte character\n"));
+				libpq_append_error(conn, "incomplete multibyte character");
 				return NULL;
 			}
 
@@ -4101,8 +4067,7 @@ PQescapeInternal(PGconn *conn, const char *str, size_t len, bool as_ident)
 	result = rp = (char *) malloc(result_size);
 	if (rp == NULL)
 	{
-		appendPQExpBufferStr(&conn->errorMessage,
-							 libpq_gettext("out of memory\n"));
+		libpq_append_error(conn, "out of memory");
 		return NULL;
 	}
 
@@ -4266,8 +4231,7 @@ PQescapeByteaInternal(PGconn *conn,
 	if (rp == NULL)
 	{
 		if (conn)
-			appendPQExpBufferStr(&conn->errorMessage,
-								 libpq_gettext("out of memory\n"));
+			libpq_append_error(conn, "out of memory");
 		return NULL;
 	}
 
diff --git a/src/interfaces/libpq/fe-misc.c b/src/interfaces/libpq/fe-misc.c
index 795500c59354..d505ed67cc1d 100644
--- a/src/interfaces/libpq/fe-misc.c
+++ b/src/interfaces/libpq/fe-misc.c
@@ -1278,3 +1278,18 @@ libpq_ngettext(const char *msgid, const char *msgid_plural, unsigned long n)
 }
 
 #endif							/* ENABLE_NLS */
+
+void
+libpq_append_error(PGconn *conn, const char *fmt, ...)
+{
+	va_list		args;
+
+	Assert(fmt[strlen(fmt) - 1] != '\n');
+
+	va_start(args, fmt);
+
+	appendPQExpBufferVA(&conn->errorMessage, libpq_gettext(fmt), args);
+	appendPQExpBufferChar(&conn->errorMessage, '\n');
+
+	va_end(args);
+}
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index c75ed63a2c62..eeef80ab2b71 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -887,6 +887,8 @@ extern char *libpq_ngettext(const char *msgid, const char *msgid_plural, unsigne
  */
 #undef _
 
+extern void libpq_append_error(PGconn *conn, const char *fmt, ...) pg_attribute_printf(2, 3);
+
 /*
  * These macros are needed to let error-handling code be portable between
  * Unix and Windows.  (ugh)
diff --git a/src/interfaces/libpq/nls.mk b/src/interfaces/libpq/nls.mk
index 9256b426c1d4..9d994ac1960a 100644
--- a/src/interfaces/libpq/nls.mk
+++ b/src/interfaces/libpq/nls.mk
@@ -1,5 +1,5 @@
 # src/interfaces/libpq/nls.mk
 CATALOG_NAME     = libpq
 GETTEXT_FILES    = fe-auth.c fe-auth-scram.c fe-connect.c fe-exec.c fe-gssapi-common.c fe-lobj.c fe-misc.c fe-protocol3.c fe-secure.c fe-secure-common.c fe-secure-gssapi.c fe-secure-openssl.c win32.c ../../port/thread.c
-GETTEXT_TRIGGERS = libpq_gettext pqInternalNotice:2
-GETTEXT_FLAGS    = libpq_gettext:1:pass-c-format pqInternalNotice:2:c-format
+GETTEXT_TRIGGERS = libpq_append_error:2 libpq_gettext pqInternalNotice:2
+GETTEXT_FLAGS    = libpq_append_error:2 libpq_gettext:1:pass-c-format pqInternalNotice:2:c-format
diff --git a/src/interfaces/libpq/pqexpbuffer.c b/src/interfaces/libpq/pqexpbuffer.c
index eb51e6d08840..65621ec3b1c8 100644
--- a/src/interfaces/libpq/pqexpbuffer.c
+++ b/src/interfaces/libpq/pqexpbuffer.c
@@ -40,7 +40,7 @@ static const char oom_buffer[1] = "";
 /* Need a char * for unconstify() compatibility */
 static const char *oom_buffer_ptr = oom_buffer;
 
-static bool appendPQExpBufferVA(PQExpBuffer str, const char *fmt, va_list args) pg_attribute_printf(2, 0);
+static bool appendPQExpBufferVA_internal(PQExpBuffer str, const char *fmt, va_list args) pg_attribute_printf(2, 0);
 
 
 /*
@@ -250,7 +250,7 @@ printfPQExpBuffer(PQExpBuffer str, const char *fmt,...)
 	{
 		errno = save_errno;
 		va_start(args, fmt);
-		done = appendPQExpBufferVA(str, fmt, args);
+		done = appendPQExpBufferVA_internal(str, fmt, args);
 		va_end(args);
 	} while (!done);
 }
@@ -278,13 +278,30 @@ appendPQExpBuffer(PQExpBuffer str, const char *fmt,...)
 	{
 		errno = save_errno;
 		va_start(args, fmt);
-		done = appendPQExpBufferVA(str, fmt, args);
+		done = appendPQExpBufferVA_internal(str, fmt, args);
 		va_end(args);
 	} while (!done);
 }
 
+void
+appendPQExpBufferVA(PQExpBuffer str, const char *fmt, va_list args)
+{
+	int			save_errno = errno;
+	bool		done;
+
+	if (PQExpBufferBroken(str))
+		return;					/* already failed */
+
+	/* Loop in case we have to retry after enlarging the buffer. */
+	do
+	{
+		errno = save_errno;
+		done = appendPQExpBufferVA_internal(str, fmt, args);
+	} while (!done);
+}
+
 /*
- * appendPQExpBufferVA
+ * appendPQExpBufferVA_internal
  * Shared guts of printfPQExpBuffer/appendPQExpBuffer.
  * Attempt to format data and append it to str.  Returns true if done
  * (either successful or hard failure), false if need to retry.
@@ -293,7 +310,7 @@ appendPQExpBuffer(PQExpBuffer str, const char *fmt,...)
  * when looping, in case the fmt contains "%m".
  */
 static bool
-appendPQExpBufferVA(PQExpBuffer str, const char *fmt, va_list args)
+appendPQExpBufferVA_internal(PQExpBuffer str, const char *fmt, va_list args)
 {
 	size_t		avail;
 	size_t		needed;
diff --git a/src/interfaces/libpq/pqexpbuffer.h b/src/interfaces/libpq/pqexpbuffer.h
index efd652c80a33..b8216b8a7bd9 100644
--- a/src/interfaces/libpq/pqexpbuffer.h
+++ b/src/interfaces/libpq/pqexpbuffer.h
@@ -157,6 +157,8 @@ extern void printfPQExpBuffer(PQExpBuffer str, const char *fmt,...) pg_attribute
  */
 extern void appendPQExpBuffer(PQExpBuffer str, const char *fmt,...) pg_attribute_printf(2, 3);
 
+extern void appendPQExpBufferVA(PQExpBuffer str, const char *fmt, va_list args) pg_attribute_printf(2, 0);
+
 /*------------------------
  * appendPQExpBufferStr
  * Append the given string to a PQExpBuffer, allocating more space
-- 
2.37.3

#10Peter Eisentraut
peter.eisentraut@enterprisedb.com
In reply to: Peter Eisentraut (#1)
2 attachment(s)
Re: libpq error message refactoring

On 25.08.22 16:34, Peter Eisentraut wrote:

libpq now contains a mix of error message strings that end with newlines
and don't end with newlines, due to some newer code paths with new ways
of passing errors around.  This has now gotten me confused a few too
many times both during development and translation.  So I looked into
whether we can unify this, similar to how we have done elsewhere (e.g.,
pg_upgrade).  I came up with the attached patch.  It's not complete, but
it shows the idea and it looks like a nice simplification to me.

I have completed this patch, taking into account the fixes discussed in
this thread.

I have split the patch in two, for review: The first is just the new
APIs, the second are the changes that apply the API everywhere.

Attachments:

v2-0001-libpq-error-message-refactoring.patchtext/plain; charset=UTF-8; name=v2-0001-libpq-error-message-refactoring.patchDownload
From 77708750733737fb02941e44de75f4e0fc26353d Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter@eisentraut.org>
Date: Wed, 12 Oct 2022 09:03:52 +0200
Subject: [PATCH v2 1/2] libpq error message refactoring

libpq now contains a mix of error message strings that end with
newlines and don't end with newlines, due to some newer code paths
with new ways of passing errors around.  This leads to confusion and
mistakes both during development and translation.

This adds new functions libpq_append_error() and
libpq_append_conn_error() that encapsulate common code paths for
producing error message strings.  Notably, these functions append the
newline, so that the string appearing in the code does not end with a
newline.  This makes (almost) all error message strings in libpq
uniform in this regard (and also consistent with how we handle it
outside of libpq code).  (There are a few exceptions that are
difficult to fit into this scheme, but they are only a few.)

Discussion: https://www.postgresql.org/message-id/flat/7c0232ef-7b44-68db-599d-b327d0640a77@enterprisedb.com
---
 src/interfaces/libpq/fe-misc.c     | 59 ++++++++++++++++++++++++++++++
 src/interfaces/libpq/libpq-int.h   |  3 ++
 src/interfaces/libpq/nls.mk        |  8 +++-
 src/interfaces/libpq/pqexpbuffer.c |  4 +-
 src/interfaces/libpq/pqexpbuffer.h | 11 ++++++
 5 files changed, 80 insertions(+), 5 deletions(-)

diff --git a/src/interfaces/libpq/fe-misc.c b/src/interfaces/libpq/fe-misc.c
index 795500c59354..9e3d38d33710 100644
--- a/src/interfaces/libpq/fe-misc.c
+++ b/src/interfaces/libpq/fe-misc.c
@@ -1278,3 +1278,62 @@ libpq_ngettext(const char *msgid, const char *msgid_plural, unsigned long n)
 }
 
 #endif							/* ENABLE_NLS */
+
+
+/*
+ * Append a formatted string to the given buffer, after translation.  A
+ * newline is automatically appended; the format should not end with a
+ * newline.
+ */
+void
+libpq_append_error(PQExpBuffer errorMessage, const char *fmt, ...)
+{
+	int			save_errno = errno;
+	bool		done;
+	va_list		args;
+
+	Assert(fmt[strlen(fmt) - 1] != '\n');
+
+	if (PQExpBufferBroken(errorMessage))
+		return;					/* already failed */
+
+	/* Loop in case we have to retry after enlarging the buffer. */
+	do
+	{
+		errno = save_errno;
+		va_start(args, fmt);
+		done = appendPQExpBufferVA(errorMessage, libpq_gettext(fmt), args);
+		va_end(args);
+	} while (!done);
+
+	appendPQExpBufferChar(errorMessage, '\n');
+}
+
+/*
+ * Append a formatted string to the error message buffer of the given
+ * connection, after translation.  A newline is automatically appended; the
+ * format should not end with a newline.
+ */
+void
+libpq_append_conn_error(PGconn *conn, const char *fmt, ...)
+{
+	int			save_errno = errno;
+	bool		done;
+	va_list		args;
+
+	Assert(fmt[strlen(fmt) - 1] != '\n');
+
+	if (PQExpBufferBroken(&conn->errorMessage))
+		return;					/* already failed */
+
+	/* Loop in case we have to retry after enlarging the buffer. */
+	do
+	{
+		errno = save_errno;
+		va_start(args, fmt);
+		done = appendPQExpBufferVA(&conn->errorMessage, libpq_gettext(fmt), args);
+		va_end(args);
+	} while (!done);
+
+	appendPQExpBufferChar(&conn->errorMessage, '\n');
+}
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index c75ed63a2c62..c24645b46965 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -887,6 +887,9 @@ extern char *libpq_ngettext(const char *msgid, const char *msgid_plural, unsigne
  */
 #undef _
 
+extern void libpq_append_error(PQExpBuffer errorMessage, const char *fmt, ...) pg_attribute_printf(2, 3);
+extern void libpq_append_conn_error(PGconn *conn, const char *fmt, ...) pg_attribute_printf(2, 3);
+
 /*
  * These macros are needed to let error-handling code be portable between
  * Unix and Windows.  (ugh)
diff --git a/src/interfaces/libpq/nls.mk b/src/interfaces/libpq/nls.mk
index 9256b426c1d4..4df544ecef56 100644
--- a/src/interfaces/libpq/nls.mk
+++ b/src/interfaces/libpq/nls.mk
@@ -1,5 +1,9 @@
 # src/interfaces/libpq/nls.mk
 CATALOG_NAME     = libpq
 GETTEXT_FILES    = fe-auth.c fe-auth-scram.c fe-connect.c fe-exec.c fe-gssapi-common.c fe-lobj.c fe-misc.c fe-protocol3.c fe-secure.c fe-secure-common.c fe-secure-gssapi.c fe-secure-openssl.c win32.c ../../port/thread.c
-GETTEXT_TRIGGERS = libpq_gettext pqInternalNotice:2
-GETTEXT_FLAGS    = libpq_gettext:1:pass-c-format pqInternalNotice:2:c-format
+GETTEXT_TRIGGERS = libpq_append_conn_error:2 \
+                   libpq_append_error:2 \
+                   libpq_gettext pqInternalNotice:2
+GETTEXT_FLAGS    = libpq_append_conn_error:2:c-format \
+                   libpq_append_error:2:c-format \
+                   libpq_gettext:1:pass-c-format pqInternalNotice:2:c-format
diff --git a/src/interfaces/libpq/pqexpbuffer.c b/src/interfaces/libpq/pqexpbuffer.c
index eb51e6d08840..10b32efdfebc 100644
--- a/src/interfaces/libpq/pqexpbuffer.c
+++ b/src/interfaces/libpq/pqexpbuffer.c
@@ -40,8 +40,6 @@ static const char oom_buffer[1] = "";
 /* Need a char * for unconstify() compatibility */
 static const char *oom_buffer_ptr = oom_buffer;
 
-static bool appendPQExpBufferVA(PQExpBuffer str, const char *fmt, va_list args) pg_attribute_printf(2, 0);
-
 
 /*
  * markPQExpBufferBroken
@@ -292,7 +290,7 @@ appendPQExpBuffer(PQExpBuffer str, const char *fmt,...)
  * Caution: callers must be sure to preserve their entry-time errno
  * when looping, in case the fmt contains "%m".
  */
-static bool
+bool
 appendPQExpBufferVA(PQExpBuffer str, const char *fmt, va_list args)
 {
 	size_t		avail;
diff --git a/src/interfaces/libpq/pqexpbuffer.h b/src/interfaces/libpq/pqexpbuffer.h
index efd652c80a33..8845783a9a7a 100644
--- a/src/interfaces/libpq/pqexpbuffer.h
+++ b/src/interfaces/libpq/pqexpbuffer.h
@@ -157,6 +157,17 @@ extern void printfPQExpBuffer(PQExpBuffer str, const char *fmt,...) pg_attribute
  */
 extern void appendPQExpBuffer(PQExpBuffer str, const char *fmt,...) pg_attribute_printf(2, 3);
 
+/*------------------------
+ * appendPQExpBufferVA
+ * Shared guts of printfPQExpBuffer/appendPQExpBuffer.
+ * Attempt to format data and append it to str.  Returns true if done
+ * (either successful or hard failure), false if need to retry.
+ *
+ * Caution: callers must be sure to preserve their entry-time errno
+ * when looping, in case the fmt contains "%m".
+ */
+extern bool appendPQExpBufferVA(PQExpBuffer str, const char *fmt, va_list args) pg_attribute_printf(2, 0);
+
 /*------------------------
  * appendPQExpBufferStr
  * Append the given string to a PQExpBuffer, allocating more space
-- 
2.37.3

v2-0002-libpq-error-message-refactoring-part-2.patchtext/plain; charset=UTF-8; name=v2-0002-libpq-error-message-refactoring-part-2.patchDownload
From 583ee09b4088aed129479e31c117cf21dbdb56ea Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter@eisentraut.org>
Date: Wed, 12 Oct 2022 09:10:23 +0200
Subject: [PATCH v2 2/2] libpq error message refactoring, part 2

This applies the new APIs to the code.
---
 src/interfaces/libpq/fe-auth-scram.c     |  87 ++--
 src/interfaces/libpq/fe-auth.c           | 124 ++----
 src/interfaces/libpq/fe-connect.c        | 527 ++++++++++-------------
 src/interfaces/libpq/fe-exec.c           | 119 ++---
 src/interfaces/libpq/fe-gssapi-common.c  |   6 +-
 src/interfaces/libpq/fe-lobj.c           |  69 +--
 src/interfaces/libpq/fe-misc.c           |  20 +-
 src/interfaces/libpq/fe-protocol3.c      |  51 +--
 src/interfaces/libpq/fe-secure-common.c  |  32 +-
 src/interfaces/libpq/fe-secure-gssapi.c  |  18 +-
 src/interfaces/libpq/fe-secure-openssl.c | 166 +++----
 src/interfaces/libpq/fe-secure.c         |  16 +-
 12 files changed, 461 insertions(+), 774 deletions(-)

diff --git a/src/interfaces/libpq/fe-auth-scram.c b/src/interfaces/libpq/fe-auth-scram.c
index 35cfd9987de2..e71626580a71 100644
--- a/src/interfaces/libpq/fe-auth-scram.c
+++ b/src/interfaces/libpq/fe-auth-scram.c
@@ -218,14 +218,12 @@ scram_exchange(void *opaq, char *input, int inputlen,
 	{
 		if (inputlen == 0)
 		{
-			appendPQExpBufferStr(&conn->errorMessage,
-								 libpq_gettext("malformed SCRAM message (empty message)\n"));
+			libpq_append_conn_error(conn, "malformed SCRAM message (empty message)");
 			goto error;
 		}
 		if (inputlen != strlen(input))
 		{
-			appendPQExpBufferStr(&conn->errorMessage,
-								 libpq_gettext("malformed SCRAM message (length mismatch)\n"));
+			libpq_append_conn_error(conn, "malformed SCRAM message (length mismatch)");
 			goto error;
 		}
 	}
@@ -268,15 +266,13 @@ scram_exchange(void *opaq, char *input, int inputlen,
 			 */
 			if (!verify_server_signature(state, success, &errstr))
 			{
-				appendPQExpBuffer(&conn->errorMessage,
-								  libpq_gettext("could not verify server signature: %s\n"), errstr);
+				libpq_append_conn_error(conn, "could not verify server signature: %s", errstr);
 				goto error;
 			}
 
 			if (!*success)
 			{
-				appendPQExpBufferStr(&conn->errorMessage,
-									 libpq_gettext("incorrect server signature\n"));
+				libpq_append_conn_error(conn, "incorrect server signature");
 			}
 			*done = true;
 			state->state = FE_SCRAM_FINISHED;
@@ -284,8 +280,7 @@ scram_exchange(void *opaq, char *input, int inputlen,
 
 		default:
 			/* shouldn't happen */
-			appendPQExpBufferStr(&conn->errorMessage,
-								 libpq_gettext("invalid SCRAM exchange state\n"));
+			libpq_append_conn_error(conn, "invalid SCRAM exchange state");
 			goto error;
 	}
 	return;
@@ -311,18 +306,18 @@ read_attr_value(char **input, char attr, PQExpBuffer errorMessage)
 
 	if (*begin != attr)
 	{
-		appendPQExpBuffer(errorMessage,
-						  libpq_gettext("malformed SCRAM message (attribute \"%c\" expected)\n"),
-						  attr);
+		libpq_append_error(errorMessage,
+						   "malformed SCRAM message (attribute \"%c\" expected)",
+						   attr);
 		return NULL;
 	}
 	begin++;
 
 	if (*begin != '=')
 	{
-		appendPQExpBuffer(errorMessage,
-						  libpq_gettext("malformed SCRAM message (expected character \"=\" for attribute \"%c\")\n"),
-						  attr);
+		libpq_append_error(errorMessage,
+						   "malformed SCRAM message (expected character \"=\" for attribute \"%c\")",
+						   attr);
 		return NULL;
 	}
 	begin++;
@@ -361,8 +356,7 @@ build_client_first_message(fe_scram_state *state)
 	 */
 	if (!pg_strong_random(raw_nonce, SCRAM_RAW_NONCE_LEN))
 	{
-		appendPQExpBufferStr(&conn->errorMessage,
-							 libpq_gettext("could not generate nonce\n"));
+		libpq_append_conn_error(conn, "could not generate nonce");
 		return NULL;
 	}
 
@@ -371,16 +365,14 @@ build_client_first_message(fe_scram_state *state)
 	state->client_nonce = malloc(encoded_len + 1);
 	if (state->client_nonce == NULL)
 	{
-		appendPQExpBufferStr(&conn->errorMessage,
-							 libpq_gettext("out of memory\n"));
+		libpq_append_conn_error(conn, "out of memory");
 		return NULL;
 	}
 	encoded_len = pg_b64_encode(raw_nonce, SCRAM_RAW_NONCE_LEN,
 								state->client_nonce, encoded_len);
 	if (encoded_len < 0)
 	{
-		appendPQExpBufferStr(&conn->errorMessage,
-							 libpq_gettext("could not encode nonce\n"));
+		libpq_append_conn_error(conn, "could not encode nonce");
 		return NULL;
 	}
 	state->client_nonce[encoded_len] = '\0';
@@ -446,8 +438,7 @@ build_client_first_message(fe_scram_state *state)
 
 oom_error:
 	termPQExpBuffer(&buf);
-	appendPQExpBufferStr(&conn->errorMessage,
-						 libpq_gettext("out of memory\n"));
+	libpq_append_conn_error(conn, "out of memory");
 	return NULL;
 }
 
@@ -569,9 +560,7 @@ build_client_final_message(fe_scram_state *state)
 								client_proof, &errstr))
 	{
 		termPQExpBuffer(&buf);
-		appendPQExpBuffer(&conn->errorMessage,
-						  libpq_gettext("could not calculate client proof: %s\n"),
-						  errstr);
+		libpq_append_conn_error(conn, "could not calculate client proof: %s", errstr);
 		return NULL;
 	}
 
@@ -586,8 +575,7 @@ build_client_final_message(fe_scram_state *state)
 	if (encoded_len < 0)
 	{
 		termPQExpBuffer(&buf);
-		appendPQExpBufferStr(&conn->errorMessage,
-							 libpq_gettext("could not encode client proof\n"));
+		libpq_append_conn_error(conn, "could not encode client proof");
 		return NULL;
 	}
 	buf.len += encoded_len;
@@ -602,8 +590,7 @@ build_client_final_message(fe_scram_state *state)
 
 oom_error:
 	termPQExpBuffer(&buf);
-	appendPQExpBufferStr(&conn->errorMessage,
-						 libpq_gettext("out of memory\n"));
+	libpq_append_conn_error(conn, "out of memory");
 	return NULL;
 }
 
@@ -623,8 +610,7 @@ read_server_first_message(fe_scram_state *state, char *input)
 	state->server_first_message = strdup(input);
 	if (state->server_first_message == NULL)
 	{
-		appendPQExpBufferStr(&conn->errorMessage,
-							 libpq_gettext("out of memory\n"));
+		libpq_append_conn_error(conn, "out of memory");
 		return false;
 	}
 
@@ -641,16 +627,14 @@ read_server_first_message(fe_scram_state *state, char *input)
 	if (strlen(nonce) < strlen(state->client_nonce) ||
 		memcmp(nonce, state->client_nonce, strlen(state->client_nonce)) != 0)
 	{
-		appendPQExpBufferStr(&conn->errorMessage,
-							 libpq_gettext("invalid SCRAM response (nonce mismatch)\n"));
+		libpq_append_conn_error(conn, "invalid SCRAM response (nonce mismatch)");
 		return false;
 	}
 
 	state->nonce = strdup(nonce);
 	if (state->nonce == NULL)
 	{
-		appendPQExpBufferStr(&conn->errorMessage,
-							 libpq_gettext("out of memory\n"));
+		libpq_append_conn_error(conn, "out of memory");
 		return false;
 	}
 
@@ -664,8 +648,7 @@ read_server_first_message(fe_scram_state *state, char *input)
 	state->salt = malloc(decoded_salt_len);
 	if (state->salt == NULL)
 	{
-		appendPQExpBufferStr(&conn->errorMessage,
-							 libpq_gettext("out of memory\n"));
+		libpq_append_conn_error(conn, "out of memory");
 		return false;
 	}
 	state->saltlen = pg_b64_decode(encoded_salt,
@@ -674,8 +657,7 @@ read_server_first_message(fe_scram_state *state, char *input)
 								   decoded_salt_len);
 	if (state->saltlen < 0)
 	{
-		appendPQExpBufferStr(&conn->errorMessage,
-							 libpq_gettext("malformed SCRAM message (invalid salt)\n"));
+		libpq_append_conn_error(conn, "malformed SCRAM message (invalid salt)");
 		return false;
 	}
 
@@ -688,14 +670,12 @@ read_server_first_message(fe_scram_state *state, char *input)
 	state->iterations = strtol(iterations_str, &endptr, 10);
 	if (*endptr != '\0' || state->iterations < 1)
 	{
-		appendPQExpBufferStr(&conn->errorMessage,
-							 libpq_gettext("malformed SCRAM message (invalid iteration count)\n"));
+		libpq_append_conn_error(conn, "malformed SCRAM message (invalid iteration count)");
 		return false;
 	}
 
 	if (*input != '\0')
-		appendPQExpBufferStr(&conn->errorMessage,
-							 libpq_gettext("malformed SCRAM message (garbage at end of server-first-message)\n"));
+		libpq_append_conn_error(conn, "malformed SCRAM message (garbage at end of server-first-message)");
 
 	return true;
 }
@@ -714,8 +694,7 @@ read_server_final_message(fe_scram_state *state, char *input)
 	state->server_final_message = strdup(input);
 	if (!state->server_final_message)
 	{
-		appendPQExpBufferStr(&conn->errorMessage,
-							 libpq_gettext("out of memory\n"));
+		libpq_append_conn_error(conn, "out of memory");
 		return false;
 	}
 
@@ -730,9 +709,8 @@ read_server_final_message(fe_scram_state *state, char *input)
 			/* read_attr_value() has appended an error message */
 			return false;
 		}
-		appendPQExpBuffer(&conn->errorMessage,
-						  libpq_gettext("error received from server in SCRAM exchange: %s\n"),
-						  errmsg);
+		libpq_append_conn_error(conn, "error received from server in SCRAM exchange: %s",
+						   errmsg);
 		return false;
 	}
 
@@ -746,15 +724,13 @@ read_server_final_message(fe_scram_state *state, char *input)
 	}
 
 	if (*input != '\0')
-		appendPQExpBufferStr(&conn->errorMessage,
-							 libpq_gettext("malformed SCRAM message (garbage at end of server-final-message)\n"));
+		libpq_append_conn_error(conn, "malformed SCRAM message (garbage at end of server-final-message)");
 
 	server_signature_len = pg_b64_dec_len(strlen(encoded_server_signature));
 	decoded_server_signature = malloc(server_signature_len);
 	if (!decoded_server_signature)
 	{
-		appendPQExpBufferStr(&conn->errorMessage,
-							 libpq_gettext("out of memory\n"));
+		libpq_append_conn_error(conn, "out of memory");
 		return false;
 	}
 
@@ -765,8 +741,7 @@ read_server_final_message(fe_scram_state *state, char *input)
 	if (server_signature_len != SCRAM_KEY_LEN)
 	{
 		free(decoded_server_signature);
-		appendPQExpBufferStr(&conn->errorMessage,
-							 libpq_gettext("malformed SCRAM message (invalid server signature)\n"));
+		libpq_append_conn_error(conn, "malformed SCRAM message (invalid server signature)");
 		return false;
 	}
 	memcpy(state->ServerSignature, decoded_server_signature, SCRAM_KEY_LEN);
diff --git a/src/interfaces/libpq/fe-auth.c b/src/interfaces/libpq/fe-auth.c
index 49a1c626f643..4a6c358bb65d 100644
--- a/src/interfaces/libpq/fe-auth.c
+++ b/src/interfaces/libpq/fe-auth.c
@@ -72,8 +72,7 @@ pg_GSS_continue(PGconn *conn, int payloadlen)
 		ginbuf.value = malloc(payloadlen);
 		if (!ginbuf.value)
 		{
-			appendPQExpBuffer(&conn->errorMessage,
-							  libpq_gettext("out of memory allocating GSSAPI buffer (%d)\n"),
+			libpq_append_conn_error(conn, "out of memory allocating GSSAPI buffer (%d)",
 							  payloadlen);
 			return STATUS_ERROR;
 		}
@@ -153,15 +152,13 @@ pg_GSS_startup(PGconn *conn, int payloadlen)
 
 	if (!(host && host[0] != '\0'))
 	{
-		appendPQExpBufferStr(&conn->errorMessage,
-							 libpq_gettext("host name must be specified\n"));
+		libpq_append_conn_error(conn, "host name must be specified");
 		return STATUS_ERROR;
 	}
 
 	if (conn->gctx)
 	{
-		appendPQExpBufferStr(&conn->errorMessage,
-							 libpq_gettext("duplicate GSS authentication request\n"));
+		libpq_append_conn_error(conn, "duplicate GSS authentication request");
 		return STATUS_ERROR;
 	}
 
@@ -225,8 +222,7 @@ pg_SSPI_continue(PGconn *conn, int payloadlen)
 		inputbuf = malloc(payloadlen);
 		if (!inputbuf)
 		{
-			appendPQExpBuffer(&conn->errorMessage,
-							  libpq_gettext("out of memory allocating SSPI buffer (%d)\n"),
+			libpq_append_conn_error(conn, "out of memory allocating SSPI buffer (%d)",
 							  payloadlen);
 			return STATUS_ERROR;
 		}
@@ -284,8 +280,7 @@ pg_SSPI_continue(PGconn *conn, int payloadlen)
 		conn->sspictx = malloc(sizeof(CtxtHandle));
 		if (conn->sspictx == NULL)
 		{
-			appendPQExpBufferStr(&conn->errorMessage,
-								 libpq_gettext("out of memory\n"));
+			libpq_append_conn_error(conn, "out of memory");
 			return STATUS_ERROR;
 		}
 		memcpy(conn->sspictx, &newContext, sizeof(CtxtHandle));
@@ -345,8 +340,7 @@ pg_SSPI_startup(PGconn *conn, int use_negotiate, int payloadlen)
 
 	if (conn->sspictx)
 	{
-		appendPQExpBufferStr(&conn->errorMessage,
-							 libpq_gettext("duplicate SSPI authentication request\n"));
+		libpq_append_conn_error(conn, "duplicate SSPI authentication request");
 		return STATUS_ERROR;
 	}
 
@@ -356,8 +350,7 @@ pg_SSPI_startup(PGconn *conn, int use_negotiate, int payloadlen)
 	conn->sspicred = malloc(sizeof(CredHandle));
 	if (conn->sspicred == NULL)
 	{
-		appendPQExpBufferStr(&conn->errorMessage,
-							 libpq_gettext("out of memory\n"));
+		libpq_append_conn_error(conn, "out of memory");
 		return STATUS_ERROR;
 	}
 
@@ -385,15 +378,13 @@ pg_SSPI_startup(PGconn *conn, int use_negotiate, int payloadlen)
 	 */
 	if (!(host && host[0] != '\0'))
 	{
-		appendPQExpBufferStr(&conn->errorMessage,
-							 libpq_gettext("host name must be specified\n"));
+		libpq_append_conn_error(conn, "host name must be specified");
 		return STATUS_ERROR;
 	}
 	conn->sspitarget = malloc(strlen(conn->krbsrvname) + strlen(host) + 2);
 	if (!conn->sspitarget)
 	{
-		appendPQExpBufferStr(&conn->errorMessage,
-							 libpq_gettext("out of memory\n"));
+		libpq_append_conn_error(conn, "out of memory");
 		return STATUS_ERROR;
 	}
 	sprintf(conn->sspitarget, "%s/%s", conn->krbsrvname, host);
@@ -427,15 +418,13 @@ pg_SASL_init(PGconn *conn, int payloadlen)
 	if (conn->channel_binding[0] == 'r' &&	/* require */
 		!conn->ssl_in_use)
 	{
-		appendPQExpBufferStr(&conn->errorMessage,
-							 libpq_gettext("channel binding required, but SSL not in use\n"));
+		libpq_append_conn_error(conn, "channel binding required, but SSL not in use");
 		goto error;
 	}
 
 	if (conn->sasl_state)
 	{
-		appendPQExpBufferStr(&conn->errorMessage,
-							 libpq_gettext("duplicate SASL authentication request\n"));
+		libpq_append_conn_error(conn, "duplicate SASL authentication request");
 		goto error;
 	}
 
@@ -493,8 +482,7 @@ pg_SASL_init(PGconn *conn, int payloadlen)
 				 */
 				if (conn->channel_binding[0] == 'r')	/* require */
 				{
-					appendPQExpBufferStr(&conn->errorMessage,
-										 libpq_gettext("channel binding is required, but client does not support it\n"));
+					libpq_append_conn_error(conn, "channel binding is required, but client does not support it");
 					goto error;
 				}
 #endif
@@ -510,8 +498,7 @@ pg_SASL_init(PGconn *conn, int payloadlen)
 				 * the client and server supported it. The SCRAM exchange
 				 * checks for that, to prevent downgrade attacks.
 				 */
-				appendPQExpBufferStr(&conn->errorMessage,
-									 libpq_gettext("server offered SCRAM-SHA-256-PLUS authentication over a non-SSL connection\n"));
+				libpq_append_conn_error(conn, "server offered SCRAM-SHA-256-PLUS authentication over a non-SSL connection");
 				goto error;
 			}
 		}
@@ -525,16 +512,14 @@ pg_SASL_init(PGconn *conn, int payloadlen)
 
 	if (!selected_mechanism)
 	{
-		appendPQExpBufferStr(&conn->errorMessage,
-							 libpq_gettext("none of the server's SASL authentication mechanisms are supported\n"));
+		libpq_append_conn_error(conn, "none of the server's SASL authentication mechanisms are supported");
 		goto error;
 	}
 
 	if (conn->channel_binding[0] == 'r' &&	/* require */
 		strcmp(selected_mechanism, SCRAM_SHA_256_PLUS_NAME) != 0)
 	{
-		appendPQExpBufferStr(&conn->errorMessage,
-							 libpq_gettext("channel binding is required, but server did not offer an authentication method that supports channel binding\n"));
+		libpq_append_conn_error(conn, "channel binding is required, but server did not offer an authentication method that supports channel binding");
 		goto error;
 	}
 
@@ -614,8 +599,7 @@ pg_SASL_init(PGconn *conn, int payloadlen)
 oom_error:
 	termPQExpBuffer(&mechanism_buf);
 	free(initialresponse);
-	appendPQExpBufferStr(&conn->errorMessage,
-						 libpq_gettext("out of memory\n"));
+	libpq_append_conn_error(conn, "out of memory");
 	return STATUS_ERROR;
 }
 
@@ -638,8 +622,7 @@ pg_SASL_continue(PGconn *conn, int payloadlen, bool final)
 	challenge = malloc(payloadlen + 1);
 	if (!challenge)
 	{
-		appendPQExpBuffer(&conn->errorMessage,
-						  libpq_gettext("out of memory allocating SASL buffer (%d)\n"),
+		libpq_append_conn_error(conn, "out of memory allocating SASL buffer (%d)",
 						  payloadlen);
 		return STATUS_ERROR;
 	}
@@ -663,8 +646,7 @@ pg_SASL_continue(PGconn *conn, int payloadlen, bool final)
 		if (outputlen != 0)
 			free(output);
 
-		appendPQExpBufferStr(&conn->errorMessage,
-							 libpq_gettext("AuthenticationSASLFinal received from server, but SASL authentication was not completed\n"));
+		libpq_append_conn_error(conn, "AuthenticationSASLFinal received from server, but SASL authentication was not completed");
 		return STATUS_ERROR;
 	}
 
@@ -674,8 +656,7 @@ pg_SASL_continue(PGconn *conn, int payloadlen, bool final)
 	 */
 	if (output == NULL && !done)
 	{
-		appendPQExpBufferStr(&conn->errorMessage,
-							 libpq_gettext("no client response found after SASL exchange success\n"));
+		libpq_append_conn_error(conn, "no client response found after SASL exchange success");
 		return STATUS_ERROR;
 	}
 
@@ -756,8 +737,7 @@ pg_local_sendauth(PGconn *conn)
 	}
 	return STATUS_OK;
 #else
-	appendPQExpBufferStr(&conn->errorMessage,
-						 libpq_gettext("SCM_CRED authentication method not supported\n"));
+	libpq_append_conn_error(conn, "SCM_CRED authentication method not supported");
 	return STATUS_ERROR;
 #endif
 }
@@ -790,8 +770,7 @@ pg_password_sendauth(PGconn *conn, const char *password, AuthRequest areq)
 				crypt_pwd = malloc(2 * (MD5_PASSWD_LEN + 1));
 				if (!crypt_pwd)
 				{
-					appendPQExpBufferStr(&conn->errorMessage,
-										 libpq_gettext("out of memory\n"));
+					libpq_append_conn_error(conn, "out of memory");
 					return STATUS_ERROR;
 				}
 
@@ -800,18 +779,14 @@ pg_password_sendauth(PGconn *conn, const char *password, AuthRequest areq)
 									strlen(conn->pguser), crypt_pwd2,
 									&errstr))
 				{
-					appendPQExpBuffer(&conn->errorMessage,
-									  libpq_gettext("could not encrypt password: %s\n"),
-									  errstr);
+					libpq_append_conn_error(conn, "could not encrypt password: %s", errstr);
 					free(crypt_pwd);
 					return STATUS_ERROR;
 				}
 				if (!pg_md5_encrypt(crypt_pwd2 + strlen("md5"), md5Salt,
 									4, crypt_pwd, &errstr))
 				{
-					appendPQExpBuffer(&conn->errorMessage,
-									  libpq_gettext("could not encrypt password: %s\n"),
-									  errstr);
+					libpq_append_conn_error(conn, "could not encrypt password: %s", errstr);
 					free(crypt_pwd);
 					return STATUS_ERROR;
 				}
@@ -858,14 +833,12 @@ check_expected_areq(AuthRequest areq, PGconn *conn)
 			case AUTH_REQ_OK:
 				if (!conn->sasl || !conn->sasl->channel_bound(conn->sasl_state))
 				{
-					appendPQExpBufferStr(&conn->errorMessage,
-										 libpq_gettext("channel binding required, but server authenticated client without channel binding\n"));
+					libpq_append_conn_error(conn, "channel binding required, but server authenticated client without channel binding");
 					result = false;
 				}
 				break;
 			default:
-				appendPQExpBufferStr(&conn->errorMessage,
-									 libpq_gettext("channel binding required but not supported by server's authentication request\n"));
+				libpq_append_conn_error(conn, "channel binding required but not supported by server's authentication request");
 				result = false;
 				break;
 		}
@@ -899,13 +872,11 @@ pg_fe_sendauth(AuthRequest areq, int payloadlen, PGconn *conn)
 			break;
 
 		case AUTH_REQ_KRB4:
-			appendPQExpBufferStr(&conn->errorMessage,
-								 libpq_gettext("Kerberos 4 authentication not supported\n"));
+			libpq_append_conn_error(conn, "Kerberos 4 authentication not supported");
 			return STATUS_ERROR;
 
 		case AUTH_REQ_KRB5:
-			appendPQExpBufferStr(&conn->errorMessage,
-								 libpq_gettext("Kerberos 5 authentication not supported\n"));
+			libpq_append_conn_error(conn, "Kerberos 5 authentication not supported");
 			return STATUS_ERROR;
 
 #if defined(ENABLE_GSS) || defined(ENABLE_SSPI)
@@ -975,8 +946,7 @@ pg_fe_sendauth(AuthRequest areq, int payloadlen, PGconn *conn)
 			/* No GSSAPI *or* SSPI support */
 		case AUTH_REQ_GSS:
 		case AUTH_REQ_GSS_CONT:
-			appendPQExpBufferStr(&conn->errorMessage,
-								 libpq_gettext("GSSAPI authentication not supported\n"));
+			libpq_append_conn_error(conn, "GSSAPI authentication not supported");
 			return STATUS_ERROR;
 #endif							/* defined(ENABLE_GSS) || defined(ENABLE_SSPI) */
 
@@ -1007,16 +977,14 @@ pg_fe_sendauth(AuthRequest areq, int payloadlen, PGconn *conn)
 			 */
 #if !defined(ENABLE_GSS)
 		case AUTH_REQ_SSPI:
-			appendPQExpBufferStr(&conn->errorMessage,
-								 libpq_gettext("SSPI authentication not supported\n"));
+			libpq_append_conn_error(conn, "SSPI authentication not supported");
 			return STATUS_ERROR;
 #endif							/* !define(ENABLE_GSS) */
 #endif							/* ENABLE_SSPI */
 
 
 		case AUTH_REQ_CRYPT:
-			appendPQExpBufferStr(&conn->errorMessage,
-								 libpq_gettext("Crypt authentication not supported\n"));
+			libpq_append_conn_error(conn, "Crypt authentication not supported");
 			return STATUS_ERROR;
 
 		case AUTH_REQ_MD5:
@@ -1082,8 +1050,7 @@ pg_fe_sendauth(AuthRequest areq, int payloadlen, PGconn *conn)
 			break;
 
 		default:
-			appendPQExpBuffer(&conn->errorMessage,
-							  libpq_gettext("authentication method %u not supported\n"), areq);
+			libpq_append_conn_error(conn, "authentication method %u not supported", areq);
 			return STATUS_ERROR;
 	}
 
@@ -1128,9 +1095,9 @@ pg_fe_getusername(uid_t user_id, PQExpBuffer errorMessage)
 	if (GetUserName(username, &namesize))
 		name = username;
 	else if (errorMessage)
-		appendPQExpBuffer(errorMessage,
-						  libpq_gettext("user name lookup failure: error code %lu\n"),
-						  GetLastError());
+		libpq_append_error(errorMessage,
+						   "user name lookup failure: error code %lu",
+						   GetLastError());
 #else
 	if (pg_get_user_name(user_id, pwdbuf, sizeof(pwdbuf)))
 		name = pwdbuf;
@@ -1142,8 +1109,7 @@ pg_fe_getusername(uid_t user_id, PQExpBuffer errorMessage)
 	{
 		result = strdup(name);
 		if (result == NULL && errorMessage)
-			appendPQExpBufferStr(errorMessage,
-								 libpq_gettext("out of memory\n"));
+			libpq_append_error(errorMessage, "out of memory");
 	}
 
 	pgunlock_thread();
@@ -1254,8 +1220,7 @@ PQencryptPasswordConn(PGconn *conn, const char *passwd, const char *user,
 		if (PQntuples(res) != 1 || PQnfields(res) != 1)
 		{
 			PQclear(res);
-			appendPQExpBufferStr(&conn->errorMessage,
-								 libpq_gettext("unexpected shape of result set returned for SHOW\n"));
+			libpq_append_conn_error(conn, "unexpected shape of result set returned for SHOW");
 			return NULL;
 		}
 		val = PQgetvalue(res, 0, 0);
@@ -1263,8 +1228,7 @@ PQencryptPasswordConn(PGconn *conn, const char *passwd, const char *user,
 		if (strlen(val) > MAX_ALGORITHM_NAME_LEN)
 		{
 			PQclear(res);
-			appendPQExpBufferStr(&conn->errorMessage,
-								 libpq_gettext("password_encryption value too long\n"));
+			libpq_append_conn_error(conn, "password_encryption value too long");
 			return NULL;
 		}
 		strcpy(algobuf, val);
@@ -1291,9 +1255,7 @@ PQencryptPasswordConn(PGconn *conn, const char *passwd, const char *user,
 
 		crypt_pwd = pg_fe_scram_build_secret(passwd, &errstr);
 		if (!crypt_pwd)
-			appendPQExpBuffer(&conn->errorMessage,
-							  libpq_gettext("could not encrypt password: %s\n"),
-							  errstr);
+			libpq_append_conn_error(conn, "could not encrypt password: %s", errstr);
 	}
 	else if (strcmp(algorithm, "md5") == 0)
 	{
@@ -1304,21 +1266,17 @@ PQencryptPasswordConn(PGconn *conn, const char *passwd, const char *user,
 
 			if (!pg_md5_encrypt(passwd, user, strlen(user), crypt_pwd, &errstr))
 			{
-				appendPQExpBuffer(&conn->errorMessage,
-								  libpq_gettext("could not encrypt password: %s\n"),
-								  errstr);
+				libpq_append_conn_error(conn, "could not encrypt password: %s", errstr);
 				free(crypt_pwd);
 				crypt_pwd = NULL;
 			}
 		}
 		else
-			appendPQExpBufferStr(&conn->errorMessage,
-								 libpq_gettext("out of memory\n"));
+			libpq_append_conn_error(conn, "out of memory");
 	}
 	else
 	{
-		appendPQExpBuffer(&conn->errorMessage,
-						  libpq_gettext("unrecognized password encryption algorithm \"%s\"\n"),
+		libpq_append_conn_error(conn, "unrecognized password encryption algorithm \"%s\"",
 						  algorithm);
 		return NULL;
 	}
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index 746e9b4f1efc..a6120bf58b89 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -896,8 +896,7 @@ fillPGconn(PGconn *conn, PQconninfoOption *connOptions)
 				*connmember = strdup(tmp);
 				if (*connmember == NULL)
 				{
-					appendPQExpBufferStr(&conn->errorMessage,
-										 libpq_gettext("out of memory\n"));
+					libpq_append_conn_error(conn, "out of memory");
 					return false;
 				}
 			}
@@ -1079,9 +1078,8 @@ connectOptions2(PGconn *conn)
 		if (more || i != conn->nconnhost)
 		{
 			conn->status = CONNECTION_BAD;
-			appendPQExpBuffer(&conn->errorMessage,
-							  libpq_gettext("could not match %d host names to %d hostaddr values\n"),
-							  count_comma_separated_elems(conn->pghost), conn->nconnhost);
+			libpq_append_conn_error(conn, "could not match %d host names to %d hostaddr values",
+							   count_comma_separated_elems(conn->pghost), conn->nconnhost);
 			return false;
 		}
 	}
@@ -1160,9 +1158,8 @@ connectOptions2(PGconn *conn)
 		else if (more || i != conn->nconnhost)
 		{
 			conn->status = CONNECTION_BAD;
-			appendPQExpBuffer(&conn->errorMessage,
-							  libpq_gettext("could not match %d port numbers to %d hosts\n"),
-							  count_comma_separated_elems(conn->pgport), conn->nconnhost);
+			libpq_append_conn_error(conn, "could not match %d port numbers to %d hosts",
+							   count_comma_separated_elems(conn->pgport), conn->nconnhost);
 			return false;
 		}
 	}
@@ -1250,9 +1247,8 @@ connectOptions2(PGconn *conn)
 			&& strcmp(conn->channel_binding, "require") != 0)
 		{
 			conn->status = CONNECTION_BAD;
-			appendPQExpBuffer(&conn->errorMessage,
-							  libpq_gettext("invalid %s value: \"%s\"\n"),
-							  "channel_binding", conn->channel_binding);
+			libpq_append_conn_error(conn, "invalid %s value: \"%s\"",
+							   "channel_binding", conn->channel_binding);
 			return false;
 		}
 	}
@@ -1276,9 +1272,8 @@ connectOptions2(PGconn *conn)
 			&& strcmp(conn->sslmode, "verify-full") != 0)
 		{
 			conn->status = CONNECTION_BAD;
-			appendPQExpBuffer(&conn->errorMessage,
-							  libpq_gettext("invalid %s value: \"%s\"\n"),
-							  "sslmode", conn->sslmode);
+			libpq_append_conn_error(conn, "invalid %s value: \"%s\"",
+							   "sslmode", conn->sslmode);
 			return false;
 		}
 
@@ -1297,9 +1292,8 @@ connectOptions2(PGconn *conn)
 			case 'r':			/* "require" */
 			case 'v':			/* "verify-ca" or "verify-full" */
 				conn->status = CONNECTION_BAD;
-				appendPQExpBuffer(&conn->errorMessage,
-								  libpq_gettext("sslmode value \"%s\" invalid when SSL support is not compiled in\n"),
-								  conn->sslmode);
+				libpq_append_conn_error(conn, "sslmode value \"%s\" invalid when SSL support is not compiled in",
+								   conn->sslmode);
 				return false;
 		}
 #endif
@@ -1318,19 +1312,17 @@ connectOptions2(PGconn *conn)
 	if (!sslVerifyProtocolVersion(conn->ssl_min_protocol_version))
 	{
 		conn->status = CONNECTION_BAD;
-		appendPQExpBuffer(&conn->errorMessage,
-						  libpq_gettext("invalid %s value: \"%s\"\n"),
-						  "ssl_min_protocol_version",
-						  conn->ssl_min_protocol_version);
+		libpq_append_conn_error(conn, "invalid %s value: \"%s\"",
+						   "ssl_min_protocol_version",
+						   conn->ssl_min_protocol_version);
 		return false;
 	}
 	if (!sslVerifyProtocolVersion(conn->ssl_max_protocol_version))
 	{
 		conn->status = CONNECTION_BAD;
-		appendPQExpBuffer(&conn->errorMessage,
-						  libpq_gettext("invalid %s value: \"%s\"\n"),
-						  "ssl_max_protocol_version",
-						  conn->ssl_max_protocol_version);
+		libpq_append_conn_error(conn, "invalid %s value: \"%s\"",
+						   "ssl_max_protocol_version",
+						   conn->ssl_max_protocol_version);
 		return false;
 	}
 
@@ -1345,8 +1337,7 @@ connectOptions2(PGconn *conn)
 								conn->ssl_max_protocol_version))
 	{
 		conn->status = CONNECTION_BAD;
-		appendPQExpBufferStr(&conn->errorMessage,
-							 libpq_gettext("invalid SSL protocol version range\n"));
+		libpq_append_conn_error(conn, "invalid SSL protocol version range");
 		return false;
 	}
 
@@ -1360,19 +1351,15 @@ connectOptions2(PGconn *conn)
 			strcmp(conn->gssencmode, "require") != 0)
 		{
 			conn->status = CONNECTION_BAD;
-			appendPQExpBuffer(&conn->errorMessage,
-							  libpq_gettext("invalid %s value: \"%s\"\n"),
-							  "gssencmode",
-							  conn->gssencmode);
+			libpq_append_conn_error(conn, "invalid %s value: \"%s\"", "gssencmode", conn->gssencmode);
 			return false;
 		}
 #ifndef ENABLE_GSS
 		if (strcmp(conn->gssencmode, "require") == 0)
 		{
 			conn->status = CONNECTION_BAD;
-			appendPQExpBuffer(&conn->errorMessage,
-							  libpq_gettext("gssencmode value \"%s\" invalid when GSSAPI support is not compiled in\n"),
-							  conn->gssencmode);
+			libpq_append_conn_error(conn, "gssencmode value \"%s\" invalid when GSSAPI support is not compiled in",
+							   conn->gssencmode);
 			return false;
 		}
 #endif
@@ -1404,10 +1391,9 @@ connectOptions2(PGconn *conn)
 		else
 		{
 			conn->status = CONNECTION_BAD;
-			appendPQExpBuffer(&conn->errorMessage,
-							  libpq_gettext("invalid %s value: \"%s\"\n"),
-							  "target_session_attrs",
-							  conn->target_session_attrs);
+			libpq_append_conn_error(conn, "invalid %s value: \"%s\"",
+							   "target_session_attrs",
+							   conn->target_session_attrs);
 			return false;
 		}
 	}
@@ -1437,8 +1423,7 @@ connectOptions2(PGconn *conn)
 
 oom_error:
 	conn->status = CONNECTION_BAD;
-	appendPQExpBufferStr(&conn->errorMessage,
-						 libpq_gettext("out of memory\n"));
+	libpq_append_conn_error(conn, "out of memory");
 	return false;
 }
 
@@ -1600,8 +1585,7 @@ PQsetdbLogin(const char *pghost, const char *pgport, const char *pgoptions,
 
 oom_error:
 	conn->status = CONNECTION_BAD;
-	appendPQExpBufferStr(&conn->errorMessage,
-						 libpq_gettext("out of memory\n"));
+	libpq_append_conn_error(conn, "out of memory");
 	return conn;
 }
 
@@ -1624,9 +1608,8 @@ connectNoDelay(PGconn *conn)
 	{
 		char		sebuf[PG_STRERROR_R_BUFLEN];
 
-		appendPQExpBuffer(&conn->errorMessage,
-						  libpq_gettext("could not set socket to TCP no delay mode: %s\n"),
-						  SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof(sebuf)));
+		libpq_append_conn_error(conn, "could not set socket to TCP no delay mode: %s",
+						   SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof(sebuf)));
 		return 0;
 	}
 #endif
@@ -1738,11 +1721,9 @@ connectFailureMessage(PGconn *conn, int errorno)
 					  SOCK_STRERROR(errorno, sebuf, sizeof(sebuf)));
 
 	if (conn->raddr.addr.ss_family == AF_UNIX)
-		appendPQExpBufferStr(&conn->errorMessage,
-							 libpq_gettext("\tIs the server running locally and accepting connections on that socket?\n"));
+		libpq_append_conn_error(conn, "\tIs the server running locally and accepting connections on that socket?");
 	else
-		appendPQExpBufferStr(&conn->errorMessage,
-							 libpq_gettext("\tIs the server running on that host and accepting TCP/IP connections?\n"));
+		libpq_append_conn_error(conn, "\tIs the server running on that host and accepting TCP/IP connections?");
 }
 
 /*
@@ -1805,9 +1786,8 @@ parse_int_param(const char *value, int *result, PGconn *conn,
 	return true;
 
 error:
-	appendPQExpBuffer(&conn->errorMessage,
-					  libpq_gettext("invalid integer value \"%s\" for connection option \"%s\"\n"),
-					  value, context);
+	libpq_append_conn_error(conn, "invalid integer value \"%s\" for connection option \"%s\"",
+					   value, context);
 	return false;
 }
 
@@ -1835,11 +1815,10 @@ setKeepalivesIdle(PGconn *conn)
 	{
 		char		sebuf[PG_STRERROR_R_BUFLEN];
 
-		appendPQExpBuffer(&conn->errorMessage,
-						  libpq_gettext("%s(%s) failed: %s\n"),
-						  "setsockopt",
-						  PG_TCP_KEEPALIVE_IDLE_STR,
-						  SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof(sebuf)));
+		libpq_append_conn_error(conn, "%s(%s) failed: %s",
+						   "setsockopt",
+						   PG_TCP_KEEPALIVE_IDLE_STR,
+						   SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof(sebuf)));
 		return 0;
 	}
 #endif
@@ -1870,11 +1849,10 @@ setKeepalivesInterval(PGconn *conn)
 	{
 		char		sebuf[PG_STRERROR_R_BUFLEN];
 
-		appendPQExpBuffer(&conn->errorMessage,
-						  libpq_gettext("%s(%s) failed: %s\n"),
-						  "setsockopt",
-						  "TCP_KEEPINTVL",
-						  SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof(sebuf)));
+		libpq_append_conn_error(conn, "%s(%s) failed: %s",
+						   "setsockopt",
+						   "TCP_KEEPINTVL",
+						   SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof(sebuf)));
 		return 0;
 	}
 #endif
@@ -1906,11 +1884,10 @@ setKeepalivesCount(PGconn *conn)
 	{
 		char		sebuf[PG_STRERROR_R_BUFLEN];
 
-		appendPQExpBuffer(&conn->errorMessage,
-						  libpq_gettext("%s(%s) failed: %s\n"),
-						  "setsockopt",
-						  "TCP_KEEPCNT",
-						  SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof(sebuf)));
+		libpq_append_conn_error(conn, "%s(%s) failed: %s",
+						   "setsockopt",
+						   "TCP_KEEPCNT",
+						   SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof(sebuf)));
 		return 0;
 	}
 #endif
@@ -1971,8 +1948,7 @@ prepKeepalivesWin32(PGconn *conn)
 
 	if (!setKeepalivesWin32(conn->sock, idle, interval))
 	{
-		appendPQExpBuffer(&conn->errorMessage,
-						  libpq_gettext("%s(%s) failed: error code %d\n"),
+		libpq_append_conn_error(conn, "%s(%s) failed: error code %d",
 						  "WSAIoctl", "SIO_KEEPALIVE_VALS",
 						  WSAGetLastError());
 		return 0;
@@ -2006,11 +1982,10 @@ setTCPUserTimeout(PGconn *conn)
 	{
 		char		sebuf[256];
 
-		appendPQExpBuffer(&conn->errorMessage,
-						  libpq_gettext("%s(%s) failed: %s\n"),
-						  "setsockopt",
-						  "TCP_USER_TIMEOUT",
-						  SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof(sebuf)));
+		libpq_append_conn_error(conn, "%s(%s) failed: %s",
+						   "setsockopt",
+						   "TCP_USER_TIMEOUT",
+						   SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof(sebuf)));
 		return 0;
 	}
 #endif
@@ -2286,8 +2261,7 @@ PQconnectPoll(PGconn *conn)
 			break;
 
 		default:
-			appendPQExpBufferStr(&conn->errorMessage,
-								 libpq_gettext("invalid connection state, probably indicative of memory corruption\n"));
+			libpq_append_conn_error(conn, "invalid connection state, probably indicative of memory corruption");
 			goto error_return;
 	}
 
@@ -2365,9 +2339,7 @@ PQconnectPoll(PGconn *conn)
 
 			if (thisport < 1 || thisport > 65535)
 			{
-				appendPQExpBuffer(&conn->errorMessage,
-								  libpq_gettext("invalid port number: \"%s\"\n"),
-								  ch->port);
+				libpq_append_conn_error(conn, "invalid port number: \"%s\"", ch->port);
 				goto keep_going;
 			}
 		}
@@ -2381,9 +2353,8 @@ PQconnectPoll(PGconn *conn)
 										 &conn->addrlist);
 				if (ret || !conn->addrlist)
 				{
-					appendPQExpBuffer(&conn->errorMessage,
-									  libpq_gettext("could not translate host name \"%s\" to address: %s\n"),
-									  ch->host, gai_strerror(ret));
+					libpq_append_conn_error(conn, "could not translate host name \"%s\" to address: %s",
+									   ch->host, gai_strerror(ret));
 					goto keep_going;
 				}
 				break;
@@ -2394,9 +2365,8 @@ PQconnectPoll(PGconn *conn)
 										 &conn->addrlist);
 				if (ret || !conn->addrlist)
 				{
-					appendPQExpBuffer(&conn->errorMessage,
-									  libpq_gettext("could not parse network address \"%s\": %s\n"),
-									  ch->hostaddr, gai_strerror(ret));
+					libpq_append_conn_error(conn, "could not parse network address \"%s\": %s",
+									   ch->hostaddr, gai_strerror(ret));
 					goto keep_going;
 				}
 				break;
@@ -2406,10 +2376,9 @@ PQconnectPoll(PGconn *conn)
 				UNIXSOCK_PATH(portstr, thisport, ch->host);
 				if (strlen(portstr) >= UNIXSOCK_PATH_BUFLEN)
 				{
-					appendPQExpBuffer(&conn->errorMessage,
-									  libpq_gettext("Unix-domain socket path \"%s\" is too long (maximum %d bytes)\n"),
-									  portstr,
-									  (int) (UNIXSOCK_PATH_BUFLEN - 1));
+					libpq_append_conn_error(conn, "Unix-domain socket path \"%s\" is too long (maximum %d bytes)",
+									   portstr,
+									   (int) (UNIXSOCK_PATH_BUFLEN - 1));
 					goto keep_going;
 				}
 
@@ -2421,9 +2390,8 @@ PQconnectPoll(PGconn *conn)
 										 &conn->addrlist);
 				if (ret || !conn->addrlist)
 				{
-					appendPQExpBuffer(&conn->errorMessage,
-									  libpq_gettext("could not translate Unix-domain socket path \"%s\" to address: %s\n"),
-									  portstr, gai_strerror(ret));
+					libpq_append_conn_error(conn, "could not translate Unix-domain socket path \"%s\" to address: %s",
+									   portstr, gai_strerror(ret));
 					goto keep_going;
 				}
 				break;
@@ -2544,9 +2512,8 @@ PQconnectPoll(PGconn *conn)
 							goto keep_going;
 						}
 						emitHostIdentityInfo(conn, host_addr);
-						appendPQExpBuffer(&conn->errorMessage,
-										  libpq_gettext("could not create socket: %s\n"),
-										  SOCK_STRERROR(errorno, sebuf, sizeof(sebuf)));
+						libpq_append_conn_error(conn, "could not create socket: %s",
+										   SOCK_STRERROR(errorno, sebuf, sizeof(sebuf)));
 						goto error_return;
 					}
 
@@ -2575,9 +2542,8 @@ PQconnectPoll(PGconn *conn)
 					}
 					if (!pg_set_noblock(conn->sock))
 					{
-						appendPQExpBuffer(&conn->errorMessage,
-										  libpq_gettext("could not set socket to nonblocking mode: %s\n"),
-										  SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof(sebuf)));
+						libpq_append_conn_error(conn, "could not set socket to nonblocking mode: %s",
+										   SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof(sebuf)));
 						conn->try_next_addr = true;
 						goto keep_going;
 					}
@@ -2585,9 +2551,8 @@ PQconnectPoll(PGconn *conn)
 #ifdef F_SETFD
 					if (fcntl(conn->sock, F_SETFD, FD_CLOEXEC) == -1)
 					{
-						appendPQExpBuffer(&conn->errorMessage,
-										  libpq_gettext("could not set socket to close-on-exec mode: %s\n"),
-										  SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof(sebuf)));
+						libpq_append_conn_error(conn, "could not set socket to close-on-exec mode: %s",
+										   SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof(sebuf)));
 						conn->try_next_addr = true;
 						goto keep_going;
 					}
@@ -2603,8 +2568,7 @@ PQconnectPoll(PGconn *conn)
 
 						if (usekeepalives < 0)
 						{
-							appendPQExpBufferStr(&conn->errorMessage,
-												 libpq_gettext("keepalives parameter must be an integer\n"));
+							libpq_append_conn_error(conn, "keepalives parameter must be an integer");
 							err = 1;
 						}
 						else if (usekeepalives == 0)
@@ -2616,11 +2580,10 @@ PQconnectPoll(PGconn *conn)
 											SOL_SOCKET, SO_KEEPALIVE,
 											(char *) &on, sizeof(on)) < 0)
 						{
-							appendPQExpBuffer(&conn->errorMessage,
-											  libpq_gettext("%s(%s) failed: %s\n"),
-											  "setsockopt",
-											  "SO_KEEPALIVE",
-											  SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof(sebuf)));
+							libpq_append_conn_error(conn, "%s(%s) failed: %s",
+											   "setsockopt",
+											   "SO_KEEPALIVE",
+											   SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof(sebuf)));
 							err = 1;
 						}
 						else if (!setKeepalivesIdle(conn)
@@ -2744,9 +2707,8 @@ PQconnectPoll(PGconn *conn)
 				if (getsockopt(conn->sock, SOL_SOCKET, SO_ERROR,
 							   (char *) &optval, &optlen) == -1)
 				{
-					appendPQExpBuffer(&conn->errorMessage,
-									  libpq_gettext("could not get socket error status: %s\n"),
-									  SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof(sebuf)));
+					libpq_append_conn_error(conn, "could not get socket error status: %s",
+									   SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof(sebuf)));
 					goto error_return;
 				}
 				else if (optval != 0)
@@ -2772,9 +2734,8 @@ PQconnectPoll(PGconn *conn)
 								(struct sockaddr *) &conn->laddr.addr,
 								&conn->laddr.salen) < 0)
 				{
-					appendPQExpBuffer(&conn->errorMessage,
-									  libpq_gettext("could not get client address from socket: %s\n"),
-									  SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof(sebuf)));
+					libpq_append_conn_error(conn, "could not get client address from socket: %s",
+									   SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof(sebuf)));
 					goto error_return;
 				}
 
@@ -2811,12 +2772,10 @@ PQconnectPoll(PGconn *conn)
 						 * stub
 						 */
 						if (errno == ENOSYS)
-							appendPQExpBufferStr(&conn->errorMessage,
-												 libpq_gettext("requirepeer parameter is not supported on this platform\n"));
+							libpq_append_conn_error(conn, "requirepeer parameter is not supported on this platform");
 						else
-							appendPQExpBuffer(&conn->errorMessage,
-											  libpq_gettext("could not get peer credentials: %s\n"),
-											  strerror_r(errno, sebuf, sizeof(sebuf)));
+							libpq_append_conn_error(conn, "could not get peer credentials: %s",
+											   strerror_r(errno, sebuf, sizeof(sebuf)));
 						goto error_return;
 					}
 
@@ -2828,9 +2787,8 @@ PQconnectPoll(PGconn *conn)
 
 					if (strcmp(remote_username, conn->requirepeer) != 0)
 					{
-						appendPQExpBuffer(&conn->errorMessage,
-										  libpq_gettext("requirepeer specifies \"%s\", but actual peer user name is \"%s\"\n"),
-										  conn->requirepeer, remote_username);
+						libpq_append_conn_error(conn, "requirepeer specifies \"%s\", but actual peer user name is \"%s\"",
+										   conn->requirepeer, remote_username);
 						free(remote_username);
 						goto error_return;
 					}
@@ -2870,9 +2828,8 @@ PQconnectPoll(PGconn *conn)
 
 					if (pqPacketSend(conn, 0, &pv, sizeof(pv)) != STATUS_OK)
 					{
-						appendPQExpBuffer(&conn->errorMessage,
-										  libpq_gettext("could not send GSSAPI negotiation packet: %s\n"),
-										  SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof(sebuf)));
+						libpq_append_conn_error(conn, "could not send GSSAPI negotiation packet: %s",
+										   SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof(sebuf)));
 						goto error_return;
 					}
 
@@ -2882,8 +2839,8 @@ PQconnectPoll(PGconn *conn)
 				}
 				else if (!conn->gctx && conn->gssencmode[0] == 'r')
 				{
-					appendPQExpBufferStr(&conn->errorMessage,
-										 libpq_gettext("GSSAPI encryption required but was impossible (possibly no credential cache, no server support, or using a local socket)\n"));
+					libpq_append_conn_error(conn,
+									   "GSSAPI encryption required but was impossible (possibly no credential cache, no server support, or using a local socket)");
 					goto error_return;
 				}
 #endif
@@ -2924,9 +2881,8 @@ PQconnectPoll(PGconn *conn)
 					pv = pg_hton32(NEGOTIATE_SSL_CODE);
 					if (pqPacketSend(conn, 0, &pv, sizeof(pv)) != STATUS_OK)
 					{
-						appendPQExpBuffer(&conn->errorMessage,
-										  libpq_gettext("could not send SSL negotiation packet: %s\n"),
-										  SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof(sebuf)));
+						libpq_append_conn_error(conn, "could not send SSL negotiation packet: %s",
+										   SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof(sebuf)));
 						goto error_return;
 					}
 					/* Ok, wait for response */
@@ -2942,8 +2898,7 @@ PQconnectPoll(PGconn *conn)
 													EnvironmentOptions);
 				if (!startpacket)
 				{
-					appendPQExpBufferStr(&conn->errorMessage,
-										 libpq_gettext("out of memory\n"));
+					libpq_append_conn_error(conn, "out of memory");
 					goto error_return;
 				}
 
@@ -2955,9 +2910,8 @@ PQconnectPoll(PGconn *conn)
 				 */
 				if (pqPacketSend(conn, 0, startpacket, packetlen) != STATUS_OK)
 				{
-					appendPQExpBuffer(&conn->errorMessage,
-									  libpq_gettext("could not send startup packet: %s\n"),
-									  SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof(sebuf)));
+					libpq_append_conn_error(conn, "could not send startup packet: %s",
+									   SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof(sebuf)));
 					free(startpacket);
 					goto error_return;
 				}
@@ -3031,8 +2985,7 @@ PQconnectPoll(PGconn *conn)
 														 * "verify-full" */
 						{
 							/* Require SSL, but server does not want it */
-							appendPQExpBufferStr(&conn->errorMessage,
-												 libpq_gettext("server does not support SSL, but SSL was required\n"));
+							libpq_append_conn_error(conn, "server does not support SSL, but SSL was required");
 							goto error_return;
 						}
 						/* Otherwise, proceed with normal startup */
@@ -3058,9 +3011,8 @@ PQconnectPoll(PGconn *conn)
 					}
 					else
 					{
-						appendPQExpBuffer(&conn->errorMessage,
-										  libpq_gettext("received invalid response to SSL negotiation: %c\n"),
-										  SSLok);
+						libpq_append_conn_error(conn, "received invalid response to SSL negotiation: %c",
+										   SSLok);
 						goto error_return;
 					}
 				}
@@ -3079,8 +3031,7 @@ PQconnectPoll(PGconn *conn)
 					 */
 					if (conn->inCursor != conn->inEnd)
 					{
-						appendPQExpBufferStr(&conn->errorMessage,
-											 libpq_gettext("received unencrypted data after SSL response\n"));
+						libpq_append_conn_error(conn, "received unencrypted data after SSL response");
 						goto error_return;
 					}
 
@@ -3160,8 +3111,7 @@ PQconnectPoll(PGconn *conn)
 						/* Server doesn't want GSSAPI; fall back if we can */
 						if (conn->gssencmode[0] == 'r')
 						{
-							appendPQExpBufferStr(&conn->errorMessage,
-												 libpq_gettext("server doesn't support GSSAPI encryption, but it was required\n"));
+							libpq_append_conn_error(conn, "server doesn't support GSSAPI encryption, but it was required");
 							goto error_return;
 						}
 
@@ -3172,9 +3122,8 @@ PQconnectPoll(PGconn *conn)
 					}
 					else if (gss_ok != 'G')
 					{
-						appendPQExpBuffer(&conn->errorMessage,
-										  libpq_gettext("received invalid response to GSSAPI negotiation: %c\n"),
-										  gss_ok);
+						libpq_append_conn_error(conn, "received invalid response to GSSAPI negotiation: %c",
+										   gss_ok);
 						goto error_return;
 					}
 				}
@@ -3191,8 +3140,7 @@ PQconnectPoll(PGconn *conn)
 					 */
 					if (conn->inCursor != conn->inEnd)
 					{
-						appendPQExpBufferStr(&conn->errorMessage,
-											 libpq_gettext("received unencrypted data after GSSAPI encryption response\n"));
+						libpq_append_conn_error(conn, "received unencrypted data after GSSAPI encryption response");
 						goto error_return;
 					}
 
@@ -3251,9 +3199,8 @@ PQconnectPoll(PGconn *conn)
 				 */
 				if (!(beresp == 'R' || beresp == 'E'))
 				{
-					appendPQExpBuffer(&conn->errorMessage,
-									  libpq_gettext("expected authentication request from server, but received %c\n"),
-									  beresp);
+					libpq_append_conn_error(conn, "expected authentication request from server, but received %c",
+									   beresp);
 					goto error_return;
 				}
 
@@ -3276,9 +3223,8 @@ PQconnectPoll(PGconn *conn)
 				 */
 				if (beresp == 'R' && (msgLength < 8 || msgLength > 2000))
 				{
-					appendPQExpBuffer(&conn->errorMessage,
-									  libpq_gettext("expected authentication request from server, but received %c\n"),
-									  beresp);
+					libpq_append_conn_error(conn, "expected authentication request from server, but received %c",
+									   beresp);
 					goto error_return;
 				}
 
@@ -3483,8 +3429,7 @@ PQconnectPoll(PGconn *conn)
 				if (res)
 				{
 					if (res->resultStatus != PGRES_FATAL_ERROR)
-						appendPQExpBufferStr(&conn->errorMessage,
-											 libpq_gettext("unexpected message from server during startup\n"));
+						libpq_append_conn_error(conn, "unexpected message from server during startup");
 					else if (conn->send_appname &&
 							 (conn->appname || conn->fbappname))
 					{
@@ -3575,11 +3520,9 @@ PQconnectPoll(PGconn *conn)
 					{
 						/* Wrong server state, reject and try the next host */
 						if (conn->target_server_type == SERVER_TYPE_READ_WRITE)
-							appendPQExpBufferStr(&conn->errorMessage,
-												 libpq_gettext("session is read-only\n"));
+							libpq_append_conn_error(conn, "session is read-only");
 						else
-							appendPQExpBufferStr(&conn->errorMessage,
-												 libpq_gettext("session is not read-only\n"));
+							libpq_append_conn_error(conn, "session is not read-only");
 
 						/* Close connection politely. */
 						conn->status = CONNECTION_OK;
@@ -3632,11 +3575,9 @@ PQconnectPoll(PGconn *conn)
 					{
 						/* Wrong server state, reject and try the next host */
 						if (conn->target_server_type == SERVER_TYPE_PRIMARY)
-							appendPQExpBufferStr(&conn->errorMessage,
-												 libpq_gettext("server is in hot standby mode\n"));
+							libpq_append_conn_error(conn, "server is in hot standby mode");
 						else
-							appendPQExpBufferStr(&conn->errorMessage,
-												 libpq_gettext("server is not in hot standby mode\n"));
+							libpq_append_conn_error(conn, "server is not in hot standby mode");
 
 						/* Close connection politely. */
 						conn->status = CONNECTION_OK;
@@ -3752,8 +3693,7 @@ PQconnectPoll(PGconn *conn)
 				PQclear(res);
 
 				/* Append error report to conn->errorMessage. */
-				appendPQExpBuffer(&conn->errorMessage,
-								  libpq_gettext("\"%s\" failed\n"),
+				libpq_append_conn_error(conn, "\"%s\" failed",
 								  "SHOW transaction_read_only");
 
 				/* Close connection politely. */
@@ -3803,8 +3743,7 @@ PQconnectPoll(PGconn *conn)
 				PQclear(res);
 
 				/* Append error report to conn->errorMessage. */
-				appendPQExpBuffer(&conn->errorMessage,
-								  libpq_gettext("\"%s\" failed\n"),
+				libpq_append_conn_error(conn, "\"%s\" failed",
 								  "SELECT pg_is_in_recovery()");
 
 				/* Close connection politely. */
@@ -3817,9 +3756,8 @@ PQconnectPoll(PGconn *conn)
 			}
 
 		default:
-			appendPQExpBuffer(&conn->errorMessage,
-							  libpq_gettext("invalid connection state %d, "
-											"probably indicative of memory corruption\n"),
+			libpq_append_conn_error(conn,
+							   "invalid connection state %d, probably indicative of memory corruption",
 							  conn->status);
 			goto error_return;
 	}
@@ -4740,7 +4678,7 @@ ldapServiceLookup(const char *purl, PQconninfoOption *options,
 
 	if ((url = strdup(purl)) == NULL)
 	{
-		appendPQExpBufferStr(errorMessage, libpq_gettext("out of memory\n"));
+		libpq_append_error(errorMessage, "out of memory");
 		return 3;
 	}
 
@@ -4752,8 +4690,8 @@ ldapServiceLookup(const char *purl, PQconninfoOption *options,
 
 	if (pg_strncasecmp(url, LDAP_URL, strlen(LDAP_URL)) != 0)
 	{
-		appendPQExpBuffer(errorMessage,
-						  libpq_gettext("invalid LDAP URL \"%s\": scheme must be ldap://\n"), purl);
+		libpq_append_error(errorMessage,
+						   "invalid LDAP URL \"%s\": scheme must be ldap://", purl);
 		free(url);
 		return 3;
 	}
@@ -4767,9 +4705,9 @@ ldapServiceLookup(const char *purl, PQconninfoOption *options,
 	p = strchr(url + strlen(LDAP_URL), '/');
 	if (p == NULL || *(p + 1) == '\0' || *(p + 1) == '?')
 	{
-		appendPQExpBuffer(errorMessage,
-						  libpq_gettext("invalid LDAP URL \"%s\": missing distinguished name\n"),
-						  purl);
+		libpq_append_error(errorMessage,
+						   "invalid LDAP URL \"%s\": missing distinguished name",
+						   purl);
 		free(url);
 		return 3;
 	}
@@ -4779,9 +4717,9 @@ ldapServiceLookup(const char *purl, PQconninfoOption *options,
 	/* attribute */
 	if ((p = strchr(dn, '?')) == NULL || *(p + 1) == '\0' || *(p + 1) == '?')
 	{
-		appendPQExpBuffer(errorMessage,
-						  libpq_gettext("invalid LDAP URL \"%s\": must have exactly one attribute\n"),
-						  purl);
+		libpq_append_error(errorMessage,
+						   "invalid LDAP URL \"%s\": must have exactly one attribute",
+						   purl);
 		free(url);
 		return 3;
 	}
@@ -4791,9 +4729,9 @@ ldapServiceLookup(const char *purl, PQconninfoOption *options,
 	/* scope */
 	if ((p = strchr(attrs[0], '?')) == NULL || *(p + 1) == '\0' || *(p + 1) == '?')
 	{
-		appendPQExpBuffer(errorMessage,
-						  libpq_gettext("invalid LDAP URL \"%s\": must have search scope (base/one/sub)\n"),
-						  purl);
+		libpq_append_error(errorMessage,
+						   "invalid LDAP URL \"%s\": must have search scope (base/one/sub)",
+						   purl);
 		free(url);
 		return 3;
 	}
@@ -4803,9 +4741,9 @@ ldapServiceLookup(const char *purl, PQconninfoOption *options,
 	/* filter */
 	if ((p = strchr(scopestr, '?')) == NULL || *(p + 1) == '\0' || *(p + 1) == '?')
 	{
-		appendPQExpBuffer(errorMessage,
-						  libpq_gettext("invalid LDAP URL \"%s\": no filter\n"),
-						  purl);
+		libpq_append_error(errorMessage,
+						   "invalid LDAP URL \"%s\": no filter",
+						   purl);
 		free(url);
 		return 3;
 	}
@@ -4825,9 +4763,9 @@ ldapServiceLookup(const char *purl, PQconninfoOption *options,
 		lport = strtol(portstr, &endptr, 10);
 		if (*portstr == '\0' || *endptr != '\0' || errno || lport < 0 || lport > 65535)
 		{
-			appendPQExpBuffer(errorMessage,
-							  libpq_gettext("invalid LDAP URL \"%s\": invalid port number\n"),
-							  purl);
+			libpq_append_error(errorMessage,
+							   "invalid LDAP URL \"%s\": invalid port number",
+							   purl);
 			free(url);
 			return 3;
 		}
@@ -4837,9 +4775,9 @@ ldapServiceLookup(const char *purl, PQconninfoOption *options,
 	/* Allow only one attribute */
 	if (strchr(attrs[0], ',') != NULL)
 	{
-		appendPQExpBuffer(errorMessage,
-						  libpq_gettext("invalid LDAP URL \"%s\": must have exactly one attribute\n"),
-						  purl);
+		libpq_append_error(errorMessage,
+						   "invalid LDAP URL \"%s\": must have exactly one attribute",
+						   purl);
 		free(url);
 		return 3;
 	}
@@ -4853,9 +4791,9 @@ ldapServiceLookup(const char *purl, PQconninfoOption *options,
 		scope = LDAP_SCOPE_SUBTREE;
 	else
 	{
-		appendPQExpBuffer(errorMessage,
-						  libpq_gettext("invalid LDAP URL \"%s\": must have search scope (base/one/sub)\n"),
-						  purl);
+		libpq_append_error(errorMessage,
+						   "invalid LDAP URL \"%s\": must have search scope (base/one/sub)",
+						   purl);
 		free(url);
 		return 3;
 	}
@@ -4863,8 +4801,7 @@ ldapServiceLookup(const char *purl, PQconninfoOption *options,
 	/* initialize LDAP structure */
 	if ((ld = ldap_init(hostname, port)) == NULL)
 	{
-		appendPQExpBufferStr(errorMessage,
-							 libpq_gettext("could not create LDAP structure\n"));
+		libpq_append_error(errorMessage, "could not create LDAP structure");
 		free(url);
 		return 3;
 	}
@@ -4939,9 +4876,7 @@ ldapServiceLookup(const char *purl, PQconninfoOption *options,
 	{
 		if (res != NULL)
 			ldap_msgfree(res);
-		appendPQExpBuffer(errorMessage,
-						  libpq_gettext("lookup on LDAP server failed: %s\n"),
-						  ldap_err2string(rc));
+		libpq_append_error(errorMessage, "lookup on LDAP server failed: %s", ldap_err2string(rc));
 		ldap_unbind(ld);
 		free(url);
 		return 1;
@@ -4950,9 +4885,10 @@ ldapServiceLookup(const char *purl, PQconninfoOption *options,
 	/* complain if there was not exactly one result */
 	if ((rc = ldap_count_entries(ld, res)) != 1)
 	{
-		appendPQExpBufferStr(errorMessage,
-							 rc ? libpq_gettext("more than one entry found on LDAP lookup\n")
-							 : libpq_gettext("no entry found on LDAP lookup\n"));
+		if (rc > 1)
+			libpq_append_error(errorMessage, "more than one entry found on LDAP lookup");
+		else
+			libpq_append_error(errorMessage, "no entry found on LDAP lookup");
 		ldap_msgfree(res);
 		ldap_unbind(ld);
 		free(url);
@@ -4963,8 +4899,7 @@ ldapServiceLookup(const char *purl, PQconninfoOption *options,
 	if ((entry = ldap_first_entry(ld, res)) == NULL)
 	{
 		/* should never happen */
-		appendPQExpBufferStr(errorMessage,
-							 libpq_gettext("no entry found on LDAP lookup\n"));
+		libpq_append_error(errorMessage, "no entry found on LDAP lookup");
 		ldap_msgfree(res);
 		ldap_unbind(ld);
 		free(url);
@@ -4974,8 +4909,7 @@ ldapServiceLookup(const char *purl, PQconninfoOption *options,
 	/* get values */
 	if ((values = ldap_get_values_len(ld, entry, attrs[0])) == NULL)
 	{
-		appendPQExpBufferStr(errorMessage,
-							 libpq_gettext("attribute has no values on LDAP lookup\n"));
+		libpq_append_error(errorMessage, "attribute has no values on LDAP lookup");
 		ldap_msgfree(res);
 		ldap_unbind(ld);
 		free(url);
@@ -4987,8 +4921,7 @@ ldapServiceLookup(const char *purl, PQconninfoOption *options,
 
 	if (values[0] == NULL)
 	{
-		appendPQExpBufferStr(errorMessage,
-							 libpq_gettext("attribute has no values on LDAP lookup\n"));
+		libpq_append_error(errorMessage, "attribute has no values on LDAP lookup");
 		ldap_value_free_len(values);
 		ldap_unbind(ld);
 		return 1;
@@ -5000,8 +4933,7 @@ ldapServiceLookup(const char *purl, PQconninfoOption *options,
 		size += values[i]->bv_len + 1;
 	if ((result = malloc(size)) == NULL)
 	{
-		appendPQExpBufferStr(errorMessage,
-							 libpq_gettext("out of memory\n"));
+		libpq_append_error(errorMessage, "out of memory");
 		ldap_value_free_len(values);
 		ldap_unbind(ld);
 		return 3;
@@ -5039,9 +4971,9 @@ ldapServiceLookup(const char *purl, PQconninfoOption *options,
 				}
 				else if (ld_is_nl_cr(*p))
 				{
-					appendPQExpBuffer(errorMessage,
-									  libpq_gettext("missing \"=\" after \"%s\" in connection info string\n"),
-									  optname);
+					libpq_append_error(errorMessage,
+									   "missing \"=\" after \"%s\" in connection info string",
+									   optname);
 					free(result);
 					return 3;
 				}
@@ -5058,9 +4990,9 @@ ldapServiceLookup(const char *purl, PQconninfoOption *options,
 				}
 				else if (!ld_is_sp_tab(*p))
 				{
-					appendPQExpBuffer(errorMessage,
-									  libpq_gettext("missing \"=\" after \"%s\" in connection info string\n"),
-									  optname);
+					libpq_append_error(errorMessage,
+									   "missing \"=\" after \"%s\" in connection info string",
+									   optname);
 					free(result);
 					return 3;
 				}
@@ -5119,8 +5051,7 @@ ldapServiceLookup(const char *purl, PQconninfoOption *options,
 						options[i].val = strdup(optval);
 						if (!options[i].val)
 						{
-							appendPQExpBufferStr(errorMessage,
-												 libpq_gettext("out of memory\n"));
+							libpq_append_error(errorMessage, "out of memory");
 							free(result);
 							return 3;
 						}
@@ -5131,9 +5062,7 @@ ldapServiceLookup(const char *purl, PQconninfoOption *options,
 			}
 			if (!found_keyword)
 			{
-				appendPQExpBuffer(errorMessage,
-								  libpq_gettext("invalid connection option \"%s\"\n"),
-								  optname);
+				libpq_append_error(errorMessage, "invalid connection option \"%s\"", optname);
 				free(result);
 				return 1;
 			}
@@ -5147,8 +5076,8 @@ ldapServiceLookup(const char *purl, PQconninfoOption *options,
 
 	if (state == 5 || state == 6)
 	{
-		appendPQExpBufferStr(errorMessage,
-							 libpq_gettext("unterminated quoted string in connection info string\n"));
+		libpq_append_error(errorMessage,
+						   "unterminated quoted string in connection info string");
 		return 3;
 	}
 
@@ -5228,8 +5157,7 @@ parseServiceInfo(PQconninfoOption *options, PQExpBuffer errorMessage)
 last_file:
 	if (!group_found)
 	{
-		appendPQExpBuffer(errorMessage,
-						  libpq_gettext("definition of service \"%s\" not found\n"), service);
+		libpq_append_error(errorMessage, "definition of service \"%s\" not found", service);
 		return 3;
 	}
 
@@ -5255,8 +5183,7 @@ parseServiceFile(const char *serviceFile,
 	f = fopen(serviceFile, "r");
 	if (f == NULL)
 	{
-		appendPQExpBuffer(errorMessage, libpq_gettext("service file \"%s\" not found\n"),
-						  serviceFile);
+		libpq_append_error(errorMessage, "service file \"%s\" not found", serviceFile);
 		return 1;
 	}
 
@@ -5268,10 +5195,10 @@ parseServiceFile(const char *serviceFile,
 
 		if (strlen(line) >= sizeof(buf) - 1)
 		{
-			appendPQExpBuffer(errorMessage,
-							  libpq_gettext("line %d too long in service file \"%s\"\n"),
-							  linenr,
-							  serviceFile);
+			libpq_append_error(errorMessage,
+							   "line %d too long in service file \"%s\"",
+							   linenr,
+							   serviceFile);
 			result = 2;
 			goto exit;
 		}
@@ -5339,10 +5266,10 @@ parseServiceFile(const char *serviceFile,
 				val = strchr(line, '=');
 				if (val == NULL)
 				{
-					appendPQExpBuffer(errorMessage,
-									  libpq_gettext("syntax error in service file \"%s\", line %d\n"),
-									  serviceFile,
-									  linenr);
+					libpq_append_error(errorMessage,
+									   "syntax error in service file \"%s\", line %d",
+									   serviceFile,
+									   linenr);
 					result = 3;
 					goto exit;
 				}
@@ -5350,10 +5277,10 @@ parseServiceFile(const char *serviceFile,
 
 				if (strcmp(key, "service") == 0)
 				{
-					appendPQExpBuffer(errorMessage,
-									  libpq_gettext("nested service specifications not supported in service file \"%s\", line %d\n"),
-									  serviceFile,
-									  linenr);
+					libpq_append_error(errorMessage,
+									   "nested service specifications not supported in service file \"%s\", line %d",
+									   serviceFile,
+									   linenr);
 					result = 3;
 					goto exit;
 				}
@@ -5371,8 +5298,7 @@ parseServiceFile(const char *serviceFile,
 							options[i].val = strdup(val);
 						if (!options[i].val)
 						{
-							appendPQExpBufferStr(errorMessage,
-												 libpq_gettext("out of memory\n"));
+							libpq_append_error(errorMessage, "out of memory");
 							result = 3;
 							goto exit;
 						}
@@ -5383,10 +5309,10 @@ parseServiceFile(const char *serviceFile,
 
 				if (!found_keyword)
 				{
-					appendPQExpBuffer(errorMessage,
-									  libpq_gettext("syntax error in service file \"%s\", line %d\n"),
-									  serviceFile,
-									  linenr);
+					libpq_append_error(errorMessage,
+									   "syntax error in service file \"%s\", line %d",
+									   serviceFile,
+									   linenr);
 					result = 3;
 					goto exit;
 				}
@@ -5452,8 +5378,7 @@ conninfo_init(PQExpBuffer errorMessage)
 	options = (PQconninfoOption *) malloc(sizeof(PQconninfoOption) * sizeof(PQconninfoOptions) / sizeof(PQconninfoOptions[0]));
 	if (options == NULL)
 	{
-		appendPQExpBufferStr(errorMessage,
-							 libpq_gettext("out of memory\n"));
+		libpq_append_error(errorMessage, "out of memory");
 		return NULL;
 	}
 	opt_dest = options;
@@ -5551,8 +5476,7 @@ conninfo_parse(const char *conninfo, PQExpBuffer errorMessage,
 	/* Need a modifiable copy of the input string */
 	if ((buf = strdup(conninfo)) == NULL)
 	{
-		appendPQExpBufferStr(errorMessage,
-							 libpq_gettext("out of memory\n"));
+		libpq_append_error(errorMessage, "out of memory");
 		PQconninfoFree(options);
 		return NULL;
 	}
@@ -5590,9 +5514,9 @@ conninfo_parse(const char *conninfo, PQExpBuffer errorMessage,
 		/* Check that there is a following '=' */
 		if (*cp != '=')
 		{
-			appendPQExpBuffer(errorMessage,
-							  libpq_gettext("missing \"=\" after \"%s\" in connection info string\n"),
-							  pname);
+			libpq_append_error(errorMessage,
+							   "missing \"=\" after \"%s\" in connection info string",
+							   pname);
 			PQconninfoFree(options);
 			free(buf);
 			return NULL;
@@ -5639,8 +5563,7 @@ conninfo_parse(const char *conninfo, PQExpBuffer errorMessage,
 			{
 				if (*cp == '\0')
 				{
-					appendPQExpBufferStr(errorMessage,
-										 libpq_gettext("unterminated quoted string in connection info string\n"));
+					libpq_append_error(errorMessage, "unterminated quoted string in connection info string");
 					PQconninfoFree(options);
 					free(buf);
 					return NULL;
@@ -5775,9 +5698,7 @@ conninfo_array_parse(const char *const *keywords, const char *const *values,
 			/* Check for invalid connection option */
 			if (option->keyword == NULL)
 			{
-				appendPQExpBuffer(errorMessage,
-								  libpq_gettext("invalid connection option \"%s\"\n"),
-								  pname);
+				libpq_append_error(errorMessage, "invalid connection option \"%s\"", pname);
 				PQconninfoFree(options);
 				PQconninfoFree(dbname_options);
 				return NULL;
@@ -5806,8 +5727,7 @@ conninfo_array_parse(const char *const *keywords, const char *const *values,
 								options[k].val = strdup(str_option->val);
 								if (!options[k].val)
 								{
-									appendPQExpBufferStr(errorMessage,
-														 libpq_gettext("out of memory\n"));
+									libpq_append_error(errorMessage, "out of memory");
 									PQconninfoFree(options);
 									PQconninfoFree(dbname_options);
 									return NULL;
@@ -5834,8 +5754,7 @@ conninfo_array_parse(const char *const *keywords, const char *const *values,
 				option->val = strdup(pvalue);
 				if (!option->val)
 				{
-					appendPQExpBufferStr(errorMessage,
-										 libpq_gettext("out of memory\n"));
+					libpq_append_error(errorMessage, "out of memory");
 					PQconninfoFree(options);
 					PQconninfoFree(dbname_options);
 					return NULL;
@@ -5906,8 +5825,7 @@ conninfo_add_defaults(PQconninfoOption *options, PQExpBuffer errorMessage)
 				if (!option->val)
 				{
 					if (errorMessage)
-						appendPQExpBufferStr(errorMessage,
-											 libpq_gettext("out of memory\n"));
+						libpq_append_error(errorMessage, "out of memory");
 					return false;
 				}
 				continue;
@@ -5930,8 +5848,7 @@ conninfo_add_defaults(PQconninfoOption *options, PQExpBuffer errorMessage)
 				if (!option->val)
 				{
 					if (errorMessage)
-						appendPQExpBufferStr(errorMessage,
-											 libpq_gettext("out of memory\n"));
+						libpq_append_error(errorMessage, "out of memory");
 					return false;
 				}
 				continue;
@@ -5948,8 +5865,7 @@ conninfo_add_defaults(PQconninfoOption *options, PQExpBuffer errorMessage)
 			if (!option->val)
 			{
 				if (errorMessage)
-					appendPQExpBufferStr(errorMessage,
-										 libpq_gettext("out of memory\n"));
+					libpq_append_error(errorMessage, "out of memory");
 				return false;
 			}
 			continue;
@@ -6049,8 +5965,7 @@ conninfo_uri_parse_options(PQconninfoOption *options, const char *uri,
 	initPQExpBuffer(&portbuf);
 	if (PQExpBufferDataBroken(hostbuf) || PQExpBufferDataBroken(portbuf))
 	{
-		appendPQExpBufferStr(errorMessage,
-							 libpq_gettext("out of memory\n"));
+		libpq_append_error(errorMessage, "out of memory");
 		goto cleanup;
 	}
 
@@ -6058,8 +5973,7 @@ conninfo_uri_parse_options(PQconninfoOption *options, const char *uri,
 	buf = strdup(uri);
 	if (buf == NULL)
 	{
-		appendPQExpBufferStr(errorMessage,
-							 libpq_gettext("out of memory\n"));
+		libpq_append_error(errorMessage, "out of memory");
 		goto cleanup;
 	}
 	start = buf;
@@ -6069,9 +5983,9 @@ conninfo_uri_parse_options(PQconninfoOption *options, const char *uri,
 	if (prefix_len == 0)
 	{
 		/* Should never happen */
-		appendPQExpBuffer(errorMessage,
-						  libpq_gettext("invalid URI propagated to internal parser routine: \"%s\"\n"),
-						  uri);
+		libpq_append_error(errorMessage,
+						   "invalid URI propagated to internal parser routine: \"%s\"",
+						   uri);
 		goto cleanup;
 	}
 	start += prefix_len;
@@ -6146,16 +6060,16 @@ conninfo_uri_parse_options(PQconninfoOption *options, const char *uri,
 				++p;
 			if (!*p)
 			{
-				appendPQExpBuffer(errorMessage,
-								  libpq_gettext("end of string reached when looking for matching \"]\" in IPv6 host address in URI: \"%s\"\n"),
-								  uri);
+				libpq_append_error(errorMessage,
+								   "end of string reached when looking for matching \"]\" in IPv6 host address in URI: \"%s\"",
+								   uri);
 				goto cleanup;
 			}
 			if (p == host)
 			{
-				appendPQExpBuffer(errorMessage,
-								  libpq_gettext("IPv6 host address may not be empty in URI: \"%s\"\n"),
-								  uri);
+				libpq_append_error(errorMessage,
+								   "IPv6 host address may not be empty in URI: \"%s\"",
+								   uri);
 				goto cleanup;
 			}
 
@@ -6168,9 +6082,9 @@ conninfo_uri_parse_options(PQconninfoOption *options, const char *uri,
 			 */
 			if (*p && *p != ':' && *p != '/' && *p != '?' && *p != ',')
 			{
-				appendPQExpBuffer(errorMessage,
-								  libpq_gettext("unexpected character \"%c\" at position %d in URI (expected \":\" or \"/\"): \"%s\"\n"),
-								  *p, (int) (p - buf + 1), uri);
+				libpq_append_error(errorMessage,
+								   "unexpected character \"%c\" at position %d in URI (expected \":\" or \"/\"): \"%s\"",
+								   *p, (int) (p - buf + 1), uri);
 				goto cleanup;
 			}
 		}
@@ -6297,9 +6211,9 @@ conninfo_uri_parse_params(char *params,
 				/* Was there '=' already? */
 				if (value != NULL)
 				{
-					appendPQExpBuffer(errorMessage,
-									  libpq_gettext("extra key/value separator \"=\" in URI query parameter: \"%s\"\n"),
-									  keyword);
+					libpq_append_error(errorMessage,
+									   "extra key/value separator \"=\" in URI query parameter: \"%s\"",
+									   keyword);
 					return false;
 				}
 				/* Cut off keyword, advance to value */
@@ -6317,9 +6231,9 @@ conninfo_uri_parse_params(char *params,
 				/* Was there '=' at all? */
 				if (value == NULL)
 				{
-					appendPQExpBuffer(errorMessage,
-									  libpq_gettext("missing key/value separator \"=\" in URI query parameter: \"%s\"\n"),
-									  keyword);
+					libpq_append_error(errorMessage,
+									   "missing key/value separator \"=\" in URI query parameter: \"%s\"",
+									   keyword);
 					return false;
 				}
 				/* Got keyword and value, go process them. */
@@ -6369,9 +6283,9 @@ conninfo_uri_parse_params(char *params,
 		{
 			/* Insert generic message if conninfo_storeval didn't give one. */
 			if (errorMessage->len == oldmsglen)
-				appendPQExpBuffer(errorMessage,
-								  libpq_gettext("invalid URI query parameter: \"%s\"\n"),
-								  keyword);
+				libpq_append_error(errorMessage,
+								   "invalid URI query parameter: \"%s\"",
+								   keyword);
 			/* And fail. */
 			if (malloced)
 			{
@@ -6416,7 +6330,7 @@ conninfo_uri_decode(const char *str, PQExpBuffer errorMessage)
 	buf = malloc(strlen(str) + 1);
 	if (buf == NULL)
 	{
-		appendPQExpBufferStr(errorMessage, libpq_gettext("out of memory\n"));
+		libpq_append_error(errorMessage, "out of memory");
 		return NULL;
 	}
 	p = buf;
@@ -6443,9 +6357,9 @@ conninfo_uri_decode(const char *str, PQExpBuffer errorMessage)
 			 */
 			if (!(get_hexdigit(*q++, &hi) && get_hexdigit(*q++, &lo)))
 			{
-				appendPQExpBuffer(errorMessage,
-								  libpq_gettext("invalid percent-encoded token: \"%s\"\n"),
-								  str);
+				libpq_append_error(errorMessage,
+								   "invalid percent-encoded token: \"%s\"",
+								   str);
 				free(buf);
 				return NULL;
 			}
@@ -6453,9 +6367,9 @@ conninfo_uri_decode(const char *str, PQExpBuffer errorMessage)
 			c = (hi << 4) | lo;
 			if (c == 0)
 			{
-				appendPQExpBuffer(errorMessage,
-								  libpq_gettext("forbidden value %%00 in percent-encoded value: \"%s\"\n"),
-								  str);
+				libpq_append_error(errorMessage,
+								   "forbidden value %%00 in percent-encoded value: \"%s\"",
+								   str);
 				free(buf);
 				return NULL;
 			}
@@ -6548,9 +6462,9 @@ conninfo_storeval(PQconninfoOption *connOptions,
 	if (option == NULL)
 	{
 		if (!ignoreMissing)
-			appendPQExpBuffer(errorMessage,
-							  libpq_gettext("invalid connection option \"%s\"\n"),
-							  keyword);
+			libpq_append_error(errorMessage,
+							   "invalid connection option \"%s\"",
+							   keyword);
 		return NULL;
 	}
 
@@ -6566,7 +6480,7 @@ conninfo_storeval(PQconninfoOption *connOptions,
 		value_copy = strdup(value);
 		if (value_copy == NULL)
 		{
-			appendPQExpBufferStr(errorMessage, libpq_gettext("out of memory\n"));
+			libpq_append_error(errorMessage, "out of memory");
 			return NULL;
 		}
 	}
@@ -7222,8 +7136,7 @@ pgpassfileWarning(PGconn *conn)
 												  PG_DIAG_SQLSTATE);
 
 		if (sqlstate && strcmp(sqlstate, ERRCODE_INVALID_PASSWORD) == 0)
-			appendPQExpBuffer(&conn->errorMessage,
-							  libpq_gettext("password retrieved from file \"%s\"\n"),
+			libpq_append_conn_error(conn, "password retrieved from file \"%s\"",
 							  conn->pgpassfile);
 	}
 }
diff --git a/src/interfaces/libpq/fe-exec.c b/src/interfaces/libpq/fe-exec.c
index 0274c1b156c6..6fa5fccc7c4e 100644
--- a/src/interfaces/libpq/fe-exec.c
+++ b/src/interfaces/libpq/fe-exec.c
@@ -828,8 +828,7 @@ pqSaveWriteError(PGconn *conn)
 		conn->write_err_msg[0] = '\0';
 	}
 	else
-		appendPQExpBufferStr(&conn->errorMessage,
-							 libpq_gettext("write to server failed\n"));
+		libpq_append_conn_error(conn, "write to server failed");
 
 	pqSaveErrorResult(conn);
 }
@@ -867,8 +866,7 @@ pqPrepareAsyncResult(PGconn *conn)
 		 * text.
 		 */
 		if (!conn->error_result)
-			appendPQExpBufferStr(&conn->errorMessage,
-								 libpq_gettext("no error text available\n"));
+			libpq_append_conn_error(conn, "no error text available");
 
 		/* Paranoia: be sure errorReported offset is sane */
 		if (conn->errorReported < 0 ||
@@ -1316,8 +1314,7 @@ pqAllocCmdQueueEntry(PGconn *conn)
 		entry = (PGcmdQueueEntry *) malloc(sizeof(PGcmdQueueEntry));
 		if (entry == NULL)
 		{
-			appendPQExpBufferStr(&conn->errorMessage,
-								 libpq_gettext("out of memory\n"));
+			libpq_append_conn_error(conn, "out of memory");
 			return NULL;
 		}
 	}
@@ -1440,15 +1437,13 @@ PQsendQueryInternal(PGconn *conn, const char *query, bool newQuery)
 	/* check the argument */
 	if (!query)
 	{
-		appendPQExpBufferStr(&conn->errorMessage,
-							 libpq_gettext("command string is a null pointer\n"));
+		libpq_append_conn_error(conn, "command string is a null pointer");
 		return 0;
 	}
 
 	if (conn->pipelineStatus != PQ_PIPELINE_OFF)
 	{
-		appendPQExpBuffer(&conn->errorMessage,
-						  libpq_gettext("%s not allowed in pipeline mode\n"),
+		libpq_append_conn_error(conn, "%s not allowed in pipeline mode",
 						  "PQsendQuery");
 		return 0;
 	}
@@ -1511,15 +1506,13 @@ PQsendQueryParams(PGconn *conn,
 	/* check the arguments */
 	if (!command)
 	{
-		appendPQExpBufferStr(&conn->errorMessage,
-							 libpq_gettext("command string is a null pointer\n"));
+		libpq_append_conn_error(conn, "command string is a null pointer");
 		return 0;
 	}
 	if (nParams < 0 || nParams > PQ_QUERY_PARAM_MAX_LIMIT)
 	{
-		appendPQExpBuffer(&conn->errorMessage,
-						  libpq_gettext("number of parameters must be between 0 and %d\n"),
-						  PQ_QUERY_PARAM_MAX_LIMIT);
+		libpq_append_conn_error(conn, "number of parameters must be between 0 and %d",
+						   PQ_QUERY_PARAM_MAX_LIMIT);
 		return 0;
 	}
 
@@ -1554,21 +1547,18 @@ PQsendPrepare(PGconn *conn,
 	/* check the arguments */
 	if (!stmtName)
 	{
-		appendPQExpBufferStr(&conn->errorMessage,
-							 libpq_gettext("statement name is a null pointer\n"));
+		libpq_append_conn_error(conn, "statement name is a null pointer");
 		return 0;
 	}
 	if (!query)
 	{
-		appendPQExpBufferStr(&conn->errorMessage,
-							 libpq_gettext("command string is a null pointer\n"));
+		libpq_append_conn_error(conn, "command string is a null pointer");
 		return 0;
 	}
 	if (nParams < 0 || nParams > PQ_QUERY_PARAM_MAX_LIMIT)
 	{
-		appendPQExpBuffer(&conn->errorMessage,
-						  libpq_gettext("number of parameters must be between 0 and %d\n"),
-						  PQ_QUERY_PARAM_MAX_LIMIT);
+		libpq_append_conn_error(conn, "number of parameters must be between 0 and %d",
+						   PQ_QUERY_PARAM_MAX_LIMIT);
 		return 0;
 	}
 
@@ -1656,15 +1646,13 @@ PQsendQueryPrepared(PGconn *conn,
 	/* check the arguments */
 	if (!stmtName)
 	{
-		appendPQExpBufferStr(&conn->errorMessage,
-							 libpq_gettext("statement name is a null pointer\n"));
+		libpq_append_conn_error(conn, "statement name is a null pointer");
 		return 0;
 	}
 	if (nParams < 0 || nParams > PQ_QUERY_PARAM_MAX_LIMIT)
 	{
-		appendPQExpBuffer(&conn->errorMessage,
-						  libpq_gettext("number of parameters must be between 0 and %d\n"),
-						  PQ_QUERY_PARAM_MAX_LIMIT);
+		libpq_append_conn_error(conn, "number of parameters must be between 0 and %d",
+						   PQ_QUERY_PARAM_MAX_LIMIT);
 		return 0;
 	}
 
@@ -1700,8 +1688,7 @@ PQsendQueryStart(PGconn *conn, bool newQuery)
 	/* Don't try to send if we know there's no live connection. */
 	if (conn->status != CONNECTION_OK)
 	{
-		appendPQExpBufferStr(&conn->errorMessage,
-							 libpq_gettext("no connection to the server\n"));
+		libpq_append_conn_error(conn, "no connection to the server");
 		return false;
 	}
 
@@ -1709,8 +1696,7 @@ PQsendQueryStart(PGconn *conn, bool newQuery)
 	if (conn->asyncStatus != PGASYNC_IDLE &&
 		conn->pipelineStatus == PQ_PIPELINE_OFF)
 	{
-		appendPQExpBufferStr(&conn->errorMessage,
-							 libpq_gettext("another command is already in progress\n"));
+		libpq_append_conn_error(conn, "another command is already in progress");
 		return false;
 	}
 
@@ -1740,8 +1726,7 @@ PQsendQueryStart(PGconn *conn, bool newQuery)
 			case PGASYNC_COPY_IN:
 			case PGASYNC_COPY_OUT:
 			case PGASYNC_COPY_BOTH:
-				appendPQExpBufferStr(&conn->errorMessage,
-									 libpq_gettext("cannot queue commands during COPY\n"));
+				libpq_append_conn_error(conn, "cannot queue commands during COPY");
 				return false;
 		}
 	}
@@ -1858,8 +1843,7 @@ PQsendQueryGuts(PGconn *conn,
 					nbytes = paramLengths[i];
 				else
 				{
-					appendPQExpBufferStr(&conn->errorMessage,
-										 libpq_gettext("length must be given for binary parameter\n"));
+					libpq_append_conn_error(conn, "length must be given for binary parameter");
 					goto sendFailed;
 				}
 			}
@@ -2181,9 +2165,7 @@ PQgetResult(PGconn *conn)
 			res = getCopyResult(conn, PGRES_COPY_BOTH);
 			break;
 		default:
-			appendPQExpBuffer(&conn->errorMessage,
-							  libpq_gettext("unexpected asyncStatus: %d\n"),
-							  (int) conn->asyncStatus);
+			libpq_append_conn_error(conn, "unexpected asyncStatus: %d", (int) conn->asyncStatus);
 			pqSaveErrorResult(conn);
 			conn->asyncStatus = PGASYNC_IDLE;	/* try to restore valid state */
 			res = pqPrepareAsyncResult(conn);
@@ -2339,8 +2321,7 @@ PQexecStart(PGconn *conn)
 
 	if (conn->pipelineStatus != PQ_PIPELINE_OFF)
 	{
-		appendPQExpBufferStr(&conn->errorMessage,
-							 libpq_gettext("synchronous command execution functions are not allowed in pipeline mode\n"));
+		libpq_append_conn_error(conn, "synchronous command execution functions are not allowed in pipeline mode");
 		return false;
 	}
 
@@ -2373,8 +2354,7 @@ PQexecStart(PGconn *conn)
 		else if (resultStatus == PGRES_COPY_BOTH)
 		{
 			/* We don't allow PQexec during COPY BOTH */
-			appendPQExpBufferStr(&conn->errorMessage,
-								 libpq_gettext("PQexec not allowed during COPY BOTH\n"));
+			libpq_append_conn_error(conn, "PQexec not allowed during COPY BOTH");
 			return false;
 		}
 		/* check for loss of connection, too */
@@ -2600,8 +2580,7 @@ PQputCopyData(PGconn *conn, const char *buffer, int nbytes)
 	if (conn->asyncStatus != PGASYNC_COPY_IN &&
 		conn->asyncStatus != PGASYNC_COPY_BOTH)
 	{
-		appendPQExpBufferStr(&conn->errorMessage,
-							 libpq_gettext("no COPY in progress\n"));
+		libpq_append_conn_error(conn, "no COPY in progress");
 		return -1;
 	}
 
@@ -2656,8 +2635,7 @@ PQputCopyEnd(PGconn *conn, const char *errormsg)
 	if (conn->asyncStatus != PGASYNC_COPY_IN &&
 		conn->asyncStatus != PGASYNC_COPY_BOTH)
 	{
-		appendPQExpBufferStr(&conn->errorMessage,
-							 libpq_gettext("no COPY in progress\n"));
+		libpq_append_conn_error(conn, "no COPY in progress");
 		return -1;
 	}
 
@@ -2725,8 +2703,7 @@ PQgetCopyData(PGconn *conn, char **buffer, int async)
 	if (conn->asyncStatus != PGASYNC_COPY_OUT &&
 		conn->asyncStatus != PGASYNC_COPY_BOTH)
 	{
-		appendPQExpBufferStr(&conn->errorMessage,
-							 libpq_gettext("no COPY in progress\n"));
+		libpq_append_conn_error(conn, "no COPY in progress");
 		return -2;
 	}
 	return pqGetCopyData3(conn, buffer, async);
@@ -2905,17 +2882,14 @@ PQfn(PGconn *conn,
 
 	if (conn->pipelineStatus != PQ_PIPELINE_OFF)
 	{
-		appendPQExpBuffer(&conn->errorMessage,
-						  libpq_gettext("%s not allowed in pipeline mode\n"),
-						  "PQfn");
+		libpq_append_conn_error(conn, "%s not allowed in pipeline mode", "PQfn");
 		return NULL;
 	}
 
 	if (conn->sock == PGINVALID_SOCKET || conn->asyncStatus != PGASYNC_IDLE ||
 		pgHavePendingResult(conn))
 	{
-		appendPQExpBufferStr(&conn->errorMessage,
-							 libpq_gettext("connection in wrong state\n"));
+		libpq_append_conn_error(conn, "connection in wrong state");
 		return NULL;
 	}
 
@@ -2958,8 +2932,7 @@ PQenterPipelineMode(PGconn *conn)
 
 	if (conn->asyncStatus != PGASYNC_IDLE)
 	{
-		appendPQExpBufferStr(&conn->errorMessage,
-							 libpq_gettext("cannot enter pipeline mode, connection not idle\n"));
+		libpq_append_conn_error(conn, "cannot enter pipeline mode, connection not idle");
 		return 0;
 	}
 
@@ -2995,13 +2968,11 @@ PQexitPipelineMode(PGconn *conn)
 		case PGASYNC_READY:
 		case PGASYNC_READY_MORE:
 			/* there are some uncollected results */
-			appendPQExpBufferStr(&conn->errorMessage,
-								 libpq_gettext("cannot exit pipeline mode with uncollected results\n"));
+			libpq_append_conn_error(conn, "cannot exit pipeline mode with uncollected results");
 			return 0;
 
 		case PGASYNC_BUSY:
-			appendPQExpBufferStr(&conn->errorMessage,
-								 libpq_gettext("cannot exit pipeline mode while busy\n"));
+			libpq_append_conn_error(conn, "cannot exit pipeline mode while busy");
 			return 0;
 
 		case PGASYNC_IDLE:
@@ -3012,15 +2983,13 @@ PQexitPipelineMode(PGconn *conn)
 		case PGASYNC_COPY_IN:
 		case PGASYNC_COPY_OUT:
 		case PGASYNC_COPY_BOTH:
-			appendPQExpBufferStr(&conn->errorMessage,
-								 libpq_gettext("cannot exit pipeline mode while in COPY\n"));
+			libpq_append_conn_error(conn, "cannot exit pipeline mode while in COPY");
 	}
 
 	/* still work to process */
 	if (conn->cmd_queue_head != NULL)
 	{
-		appendPQExpBufferStr(&conn->errorMessage,
-							 libpq_gettext("cannot exit pipeline mode with uncollected results\n"));
+		libpq_append_conn_error(conn, "cannot exit pipeline mode with uncollected results");
 		return 0;
 	}
 
@@ -3135,8 +3104,7 @@ pqPipelineProcessQueue(PGconn *conn)
 		conn->result = PQmakeEmptyPGresult(conn, PGRES_PIPELINE_ABORTED);
 		if (!conn->result)
 		{
-			appendPQExpBufferStr(&conn->errorMessage,
-								 libpq_gettext("out of memory\n"));
+			libpq_append_conn_error(conn, "out of memory");
 			pqSaveErrorResult(conn);
 			return;
 		}
@@ -3179,8 +3147,7 @@ PQpipelineSync(PGconn *conn)
 
 	if (conn->pipelineStatus == PQ_PIPELINE_OFF)
 	{
-		appendPQExpBufferStr(&conn->errorMessage,
-							 libpq_gettext("cannot send pipeline when not in pipeline mode\n"));
+		libpq_append_conn_error(conn, "cannot send pipeline when not in pipeline mode");
 		return 0;
 	}
 
@@ -3246,8 +3213,7 @@ PQsendFlushRequest(PGconn *conn)
 	/* Don't try to send if we know there's no live connection. */
 	if (conn->status != CONNECTION_OK)
 	{
-		appendPQExpBufferStr(&conn->errorMessage,
-							 libpq_gettext("no connection to the server\n"));
+		libpq_append_conn_error(conn, "no connection to the server");
 		return 0;
 	}
 
@@ -3255,8 +3221,7 @@ PQsendFlushRequest(PGconn *conn)
 	if (conn->asyncStatus != PGASYNC_IDLE &&
 		conn->pipelineStatus == PQ_PIPELINE_OFF)
 	{
-		appendPQExpBufferStr(&conn->errorMessage,
-							 libpq_gettext("another command is already in progress\n"));
+		libpq_append_conn_error(conn, "another command is already in progress");
 		return 0;
 	}
 
@@ -3992,8 +3957,7 @@ PQescapeStringInternal(PGconn *conn,
 			if (error)
 				*error = 1;
 			if (conn)
-				appendPQExpBufferStr(&conn->errorMessage,
-									 libpq_gettext("incomplete multibyte character\n"));
+				libpq_append_conn_error(conn, "incomplete multibyte character");
 			for (; i < len; i++)
 			{
 				if (((size_t) (target - to)) / 2 >= length)
@@ -4083,8 +4047,7 @@ PQescapeInternal(PGconn *conn, const char *str, size_t len, bool as_ident)
 			/* Multibyte character overruns allowable length. */
 			if ((s - str) + charlen > len || memchr(s, 0, charlen) != NULL)
 			{
-				appendPQExpBufferStr(&conn->errorMessage,
-									 libpq_gettext("incomplete multibyte character\n"));
+				libpq_append_conn_error(conn, "incomplete multibyte character");
 				return NULL;
 			}
 
@@ -4101,8 +4064,7 @@ PQescapeInternal(PGconn *conn, const char *str, size_t len, bool as_ident)
 	result = rp = (char *) malloc(result_size);
 	if (rp == NULL)
 	{
-		appendPQExpBufferStr(&conn->errorMessage,
-							 libpq_gettext("out of memory\n"));
+		libpq_append_conn_error(conn, "out of memory");
 		return NULL;
 	}
 
@@ -4266,8 +4228,7 @@ PQescapeByteaInternal(PGconn *conn,
 	if (rp == NULL)
 	{
 		if (conn)
-			appendPQExpBufferStr(&conn->errorMessage,
-								 libpq_gettext("out of memory\n"));
+			libpq_append_conn_error(conn, "out of memory");
 		return NULL;
 	}
 
diff --git a/src/interfaces/libpq/fe-gssapi-common.c b/src/interfaces/libpq/fe-gssapi-common.c
index fa08526ee2a3..2b31b8ec587f 100644
--- a/src/interfaces/libpq/fe-gssapi-common.c
+++ b/src/interfaces/libpq/fe-gssapi-common.c
@@ -94,8 +94,7 @@ pg_GSS_load_servicename(PGconn *conn)
 	host = PQhost(conn);
 	if (!(host && host[0] != '\0'))
 	{
-		appendPQExpBufferStr(&conn->errorMessage,
-							 libpq_gettext("host name must be specified\n"));
+		libpq_append_conn_error(conn, "host name must be specified");
 		return STATUS_ERROR;
 	}
 
@@ -107,8 +106,7 @@ pg_GSS_load_servicename(PGconn *conn)
 	temp_gbuf.value = (char *) malloc(maxlen);
 	if (!temp_gbuf.value)
 	{
-		appendPQExpBufferStr(&conn->errorMessage,
-							 libpq_gettext("out of memory\n"));
+		libpq_append_conn_error(conn, "out of memory");
 		return STATUS_ERROR;
 	}
 	snprintf(temp_gbuf.value, maxlen, "%s@%s",
diff --git a/src/interfaces/libpq/fe-lobj.c b/src/interfaces/libpq/fe-lobj.c
index 075a5ed85bcf..bcd228cef130 100644
--- a/src/interfaces/libpq/fe-lobj.c
+++ b/src/interfaces/libpq/fe-lobj.c
@@ -141,8 +141,7 @@ lo_truncate(PGconn *conn, int fd, size_t len)
 	/* Must check this on-the-fly because it's not there pre-8.3 */
 	if (conn->lobjfuncs->fn_lo_truncate == 0)
 	{
-		appendPQExpBuffer(&conn->errorMessage,
-						  libpq_gettext("cannot determine OID of function %s\n"),
+		libpq_append_conn_error(conn, "cannot determine OID of function %s",
 						  "lo_truncate");
 		return -1;
 	}
@@ -158,8 +157,7 @@ lo_truncate(PGconn *conn, int fd, size_t len)
 	 */
 	if (len > (size_t) INT_MAX)
 	{
-		appendPQExpBufferStr(&conn->errorMessage,
-							 libpq_gettext("argument of lo_truncate exceeds integer range\n"));
+		libpq_append_conn_error(conn, "argument of lo_truncate exceeds integer range");
 		return -1;
 	}
 
@@ -206,8 +204,7 @@ lo_truncate64(PGconn *conn, int fd, pg_int64 len)
 
 	if (conn->lobjfuncs->fn_lo_truncate64 == 0)
 	{
-		appendPQExpBuffer(&conn->errorMessage,
-						  libpq_gettext("cannot determine OID of function %s\n"),
+		libpq_append_conn_error(conn, "cannot determine OID of function %s",
 						  "lo_truncate64");
 		return -1;
 	}
@@ -262,8 +259,7 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len)
 	 */
 	if (len > (size_t) INT_MAX)
 	{
-		appendPQExpBufferStr(&conn->errorMessage,
-							 libpq_gettext("argument of lo_read exceeds integer range\n"));
+		libpq_append_conn_error(conn, "argument of lo_read exceeds integer range");
 		return -1;
 	}
 
@@ -314,8 +310,7 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len)
 	 */
 	if (len > (size_t) INT_MAX)
 	{
-		appendPQExpBufferStr(&conn->errorMessage,
-							 libpq_gettext("argument of lo_write exceeds integer range\n"));
+		libpq_append_conn_error(conn, "argument of lo_write exceeds integer range");
 		return -1;
 	}
 
@@ -399,8 +394,7 @@ lo_lseek64(PGconn *conn, int fd, pg_int64 offset, int whence)
 
 	if (conn->lobjfuncs->fn_lo_lseek64 == 0)
 	{
-		appendPQExpBuffer(&conn->errorMessage,
-						  libpq_gettext("cannot determine OID of function %s\n"),
+		libpq_append_conn_error(conn, "cannot determine OID of function %s",
 						  "lo_lseek64");
 		return -1;
 	}
@@ -490,8 +484,7 @@ lo_create(PGconn *conn, Oid lobjId)
 	/* Must check this on-the-fly because it's not there pre-8.1 */
 	if (conn->lobjfuncs->fn_lo_create == 0)
 	{
-		appendPQExpBuffer(&conn->errorMessage,
-						  libpq_gettext("cannot determine OID of function %s\n"),
+		libpq_append_conn_error(conn, "cannot determine OID of function %s",
 						  "lo_create");
 		return InvalidOid;
 	}
@@ -564,8 +557,7 @@ lo_tell64(PGconn *conn, int fd)
 
 	if (conn->lobjfuncs->fn_lo_tell64 == 0)
 	{
-		appendPQExpBuffer(&conn->errorMessage,
-						  libpq_gettext("cannot determine OID of function %s\n"),
+		libpq_append_conn_error(conn, "cannot determine OID of function %s",
 						  "lo_tell64");
 		return -1;
 	}
@@ -674,8 +666,7 @@ lo_import_internal(PGconn *conn, const char *filename, Oid oid)
 	fd = open(filename, O_RDONLY | PG_BINARY, 0666);
 	if (fd < 0)
 	{							/* error */
-		appendPQExpBuffer(&conn->errorMessage,
-						  libpq_gettext("could not open file \"%s\": %s\n"),
+		libpq_append_conn_error(conn, "could not open file \"%s\": %s",
 						  filename, strerror_r(errno, sebuf, sizeof(sebuf)));
 		return InvalidOid;
 	}
@@ -731,8 +722,7 @@ lo_import_internal(PGconn *conn, const char *filename, Oid oid)
 		(void) close(fd);
 		/* deliberately overwrite any error from lo_close */
 		pqClearConnErrorState(conn);
-		appendPQExpBuffer(&conn->errorMessage,
-						  libpq_gettext("could not read from file \"%s\": %s\n"),
+		libpq_append_conn_error(conn, "could not read from file \"%s\": %s",
 						  filename,
 						  strerror_r(save_errno, sebuf, sizeof(sebuf)));
 		return InvalidOid;
@@ -787,8 +777,7 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename)
 		(void) lo_close(conn, lobj);
 		/* deliberately overwrite any error from lo_close */
 		pqClearConnErrorState(conn);
-		appendPQExpBuffer(&conn->errorMessage,
-						  libpq_gettext("could not open file \"%s\": %s\n"),
+		libpq_append_conn_error(conn, "could not open file \"%s\": %s",
 						  filename,
 						  strerror_r(save_errno, sebuf, sizeof(sebuf)));
 		return -1;
@@ -809,8 +798,7 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename)
 			(void) close(fd);
 			/* deliberately overwrite any error from lo_close */
 			pqClearConnErrorState(conn);
-			appendPQExpBuffer(&conn->errorMessage,
-							  libpq_gettext("could not write to file \"%s\": %s\n"),
+			libpq_append_conn_error(conn, "could not write to file \"%s\": %s",
 							  filename,
 							  strerror_r(save_errno, sebuf, sizeof(sebuf)));
 			return -1;
@@ -833,8 +821,7 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename)
 	/* if we already failed, don't overwrite that msg with a close error */
 	if (close(fd) != 0 && result >= 0)
 	{
-		appendPQExpBuffer(&conn->errorMessage,
-						  libpq_gettext("could not write to file \"%s\": %s\n"),
+		libpq_append_conn_error(conn, "could not write to file \"%s\": %s",
 						  filename, strerror_r(errno, sebuf, sizeof(sebuf)));
 		result = -1;
 	}
@@ -880,8 +867,7 @@ lo_initialize(PGconn *conn)
 	lobjfuncs = (PGlobjfuncs *) malloc(sizeof(PGlobjfuncs));
 	if (lobjfuncs == NULL)
 	{
-		appendPQExpBufferStr(&conn->errorMessage,
-							 libpq_gettext("out of memory\n"));
+		libpq_append_conn_error(conn, "out of memory");
 		return -1;
 	}
 	MemSet((char *) lobjfuncs, 0, sizeof(PGlobjfuncs));
@@ -919,8 +905,7 @@ lo_initialize(PGconn *conn)
 	{
 		free(lobjfuncs);
 		PQclear(res);
-		appendPQExpBufferStr(&conn->errorMessage,
-							 libpq_gettext("query to initialize large object functions did not return data\n"));
+		libpq_append_conn_error(conn, "query to initialize large object functions did not return data");
 		return -1;
 	}
 
@@ -968,64 +953,56 @@ lo_initialize(PGconn *conn)
 	 */
 	if (lobjfuncs->fn_lo_open == 0)
 	{
-		appendPQExpBuffer(&conn->errorMessage,
-						  libpq_gettext("cannot determine OID of function %s\n"),
+		libpq_append_conn_error(conn, "cannot determine OID of function %s",
 						  "lo_open");
 		free(lobjfuncs);
 		return -1;
 	}
 	if (lobjfuncs->fn_lo_close == 0)
 	{
-		appendPQExpBuffer(&conn->errorMessage,
-						  libpq_gettext("cannot determine OID of function %s\n"),
+		libpq_append_conn_error(conn, "cannot determine OID of function %s",
 						  "lo_close");
 		free(lobjfuncs);
 		return -1;
 	}
 	if (lobjfuncs->fn_lo_creat == 0)
 	{
-		appendPQExpBuffer(&conn->errorMessage,
-						  libpq_gettext("cannot determine OID of function %s\n"),
+		libpq_append_conn_error(conn, "cannot determine OID of function %s",
 						  "lo_creat");
 		free(lobjfuncs);
 		return -1;
 	}
 	if (lobjfuncs->fn_lo_unlink == 0)
 	{
-		appendPQExpBuffer(&conn->errorMessage,
-						  libpq_gettext("cannot determine OID of function %s\n"),
+		libpq_append_conn_error(conn, "cannot determine OID of function %s",
 						  "lo_unlink");
 		free(lobjfuncs);
 		return -1;
 	}
 	if (lobjfuncs->fn_lo_lseek == 0)
 	{
-		appendPQExpBuffer(&conn->errorMessage,
-						  libpq_gettext("cannot determine OID of function %s\n"),
+		libpq_append_conn_error(conn, "cannot determine OID of function %s",
 						  "lo_lseek");
 		free(lobjfuncs);
 		return -1;
 	}
 	if (lobjfuncs->fn_lo_tell == 0)
 	{
-		appendPQExpBuffer(&conn->errorMessage,
-						  libpq_gettext("cannot determine OID of function %s\n"),
+		libpq_append_conn_error(conn, "cannot determine OID of function %s",
 						  "lo_tell");
 		free(lobjfuncs);
 		return -1;
 	}
 	if (lobjfuncs->fn_lo_read == 0)
 	{
-		appendPQExpBuffer(&conn->errorMessage,
-						  libpq_gettext("cannot determine OID of function %s\n"),
+		libpq_append_conn_error(conn, "cannot determine OID of function %s",
 						  "loread");
 		free(lobjfuncs);
 		return -1;
 	}
 	if (lobjfuncs->fn_lo_write == 0)
 	{
-		appendPQExpBuffer(&conn->errorMessage,
-						  libpq_gettext("cannot determine OID of function %s\n"),
+		libpq_append_conn_error(conn, "cannot determine OID of function %s",
 						  "lowrite");
 		free(lobjfuncs);
 		return -1;
diff --git a/src/interfaces/libpq/fe-misc.c b/src/interfaces/libpq/fe-misc.c
index 9e3d38d33710..8af918d16418 100644
--- a/src/interfaces/libpq/fe-misc.c
+++ b/src/interfaces/libpq/fe-misc.c
@@ -570,8 +570,7 @@ pqReadData(PGconn *conn)
 
 	if (conn->sock == PGINVALID_SOCKET)
 	{
-		appendPQExpBufferStr(&conn->errorMessage,
-							 libpq_gettext("connection not open\n"));
+		libpq_append_conn_error(conn, "connection not open");
 		return -1;
 	}
 
@@ -749,10 +748,9 @@ pqReadData(PGconn *conn)
 	 * means the connection has been closed.  Cope.
 	 */
 definitelyEOF:
-	appendPQExpBufferStr(&conn->errorMessage,
-						 libpq_gettext("server closed the connection unexpectedly\n"
-									   "\tThis probably means the server terminated abnormally\n"
-									   "\tbefore or while processing the request.\n"));
+	libpq_append_conn_error(conn, "server closed the connection unexpectedly\n"
+					   "\tThis probably means the server terminated abnormally\n"
+					   "\tbefore or while processing the request.");
 
 	/* Come here if lower-level code already set a suitable errorMessage */
 definitelyFailed:
@@ -1002,8 +1000,7 @@ pqWaitTimed(int forRead, int forWrite, PGconn *conn, time_t finish_time)
 
 	if (result == 0)
 	{
-		appendPQExpBufferStr(&conn->errorMessage,
-							 libpq_gettext("timeout expired\n"));
+		libpq_append_conn_error(conn, "timeout expired");
 		return 1;
 	}
 
@@ -1047,8 +1044,7 @@ pqSocketCheck(PGconn *conn, int forRead, int forWrite, time_t end_time)
 		return -1;
 	if (conn->sock == PGINVALID_SOCKET)
 	{
-		appendPQExpBufferStr(&conn->errorMessage,
-							 libpq_gettext("invalid socket\n"));
+		libpq_append_conn_error(conn, "invalid socket");
 		return -1;
 	}
 
@@ -1070,9 +1066,7 @@ pqSocketCheck(PGconn *conn, int forRead, int forWrite, time_t end_time)
 	{
 		char		sebuf[PG_STRERROR_R_BUFLEN];
 
-		appendPQExpBuffer(&conn->errorMessage,
-						  libpq_gettext("%s() failed: %s\n"),
-						  "select",
+		libpq_append_conn_error(conn, "%s() failed: %s", "select",
 						  SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof(sebuf)));
 	}
 
diff --git a/src/interfaces/libpq/fe-protocol3.c b/src/interfaces/libpq/fe-protocol3.c
index f001137b7692..88dd360c905f 100644
--- a/src/interfaces/libpq/fe-protocol3.c
+++ b/src/interfaces/libpq/fe-protocol3.c
@@ -201,8 +201,7 @@ pqParseInput3(PGconn *conn)
 														   PGRES_COMMAND_OK);
 						if (!conn->result)
 						{
-							appendPQExpBufferStr(&conn->errorMessage,
-												 libpq_gettext("out of memory"));
+							libpq_append_conn_error(conn, "out of memory");
 							pqSaveErrorResult(conn);
 						}
 					}
@@ -226,8 +225,7 @@ pqParseInput3(PGconn *conn)
 														   PGRES_PIPELINE_SYNC);
 						if (!conn->result)
 						{
-							appendPQExpBufferStr(&conn->errorMessage,
-												 libpq_gettext("out of memory"));
+							libpq_append_conn_error(conn, "out of memory");
 							pqSaveErrorResult(conn);
 						}
 						else
@@ -255,8 +253,7 @@ pqParseInput3(PGconn *conn)
 														   PGRES_EMPTY_QUERY);
 						if (!conn->result)
 						{
-							appendPQExpBufferStr(&conn->errorMessage,
-												 libpq_gettext("out of memory"));
+							libpq_append_conn_error(conn, "out of memory");
 							pqSaveErrorResult(conn);
 						}
 					}
@@ -273,8 +270,7 @@ pqParseInput3(PGconn *conn)
 															   PGRES_COMMAND_OK);
 							if (!conn->result)
 							{
-								appendPQExpBufferStr(&conn->errorMessage,
-													 libpq_gettext("out of memory"));
+								libpq_append_conn_error(conn, "out of memory");
 								pqSaveErrorResult(conn);
 							}
 						}
@@ -354,8 +350,7 @@ pqParseInput3(PGconn *conn)
 															   PGRES_COMMAND_OK);
 							if (!conn->result)
 							{
-								appendPQExpBufferStr(&conn->errorMessage,
-													 libpq_gettext("out of memory"));
+								libpq_append_conn_error(conn, "out of memory");
 								pqSaveErrorResult(conn);
 							}
 						}
@@ -387,8 +382,7 @@ pqParseInput3(PGconn *conn)
 					else
 					{
 						/* Set up to report error at end of query */
-						appendPQExpBufferStr(&conn->errorMessage,
-											 libpq_gettext("server sent data (\"D\" message) without prior row description (\"T\" message)\n"));
+						libpq_append_conn_error(conn, "server sent data (\"D\" message) without prior row description (\"T\" message)");
 						pqSaveErrorResult(conn);
 						/* Discard the unexpected message */
 						conn->inCursor += msgLength;
@@ -430,9 +424,7 @@ pqParseInput3(PGconn *conn)
 					 */
 					break;
 				default:
-					appendPQExpBuffer(&conn->errorMessage,
-									  libpq_gettext("unexpected response from server; first received character was \"%c\"\n"),
-									  id);
+					libpq_append_conn_error(conn, "unexpected response from server; first received character was \"%c\"", id);
 					/* build an error result holding the error message */
 					pqSaveErrorResult(conn);
 					/* not sure if we will see more, so go to ready state */
@@ -455,9 +447,7 @@ pqParseInput3(PGconn *conn)
 		else
 		{
 			/* Trouble --- report it */
-			appendPQExpBuffer(&conn->errorMessage,
-							  libpq_gettext("message contents do not agree with length in message type \"%c\"\n"),
-							  id);
+			libpq_append_conn_error(conn, "message contents do not agree with length in message type \"%c\"", id);
 			/* build an error result holding the error message */
 			pqSaveErrorResult(conn);
 			conn->asyncStatus = PGASYNC_READY;
@@ -475,8 +465,7 @@ pqParseInput3(PGconn *conn)
 static void
 handleSyncLoss(PGconn *conn, char id, int msgLength)
 {
-	appendPQExpBuffer(&conn->errorMessage,
-					  libpq_gettext("lost synchronization with server: got message type \"%c\", length %d\n"),
+	libpq_append_conn_error(conn, "lost synchronization with server: got message type \"%c\", length %d",
 					  id, msgLength);
 	/* build an error result holding the error message */
 	pqSaveErrorResult(conn);
@@ -967,8 +956,7 @@ pqGetErrorNotice3(PGconn *conn, bool isError)
 		}
 
 		if (PQExpBufferDataBroken(workBuf))
-			appendPQExpBufferStr(&conn->errorMessage,
-								 libpq_gettext("out of memory\n"));
+			libpq_append_conn_error(conn, "out of memory");
 		else
 			appendPQExpBufferStr(&conn->errorMessage, workBuf.data);
 	}
@@ -1723,8 +1711,7 @@ pqGetCopyData3(PGconn *conn, char **buffer, int async)
 			*buffer = (char *) malloc(msgLength + 1);
 			if (*buffer == NULL)
 			{
-				appendPQExpBufferStr(&conn->errorMessage,
-									 libpq_gettext("out of memory\n"));
+				libpq_append_conn_error(conn, "out of memory");
 				return -2;
 			}
 			memcpy(*buffer, &conn->inBuffer[conn->inCursor], msgLength);
@@ -1756,8 +1743,7 @@ pqGetline3(PGconn *conn, char *s, int maxlen)
 		 conn->asyncStatus != PGASYNC_COPY_BOTH) ||
 		conn->copy_is_binary)
 	{
-		appendPQExpBufferStr(&conn->errorMessage,
-							 libpq_gettext("PQgetline: not doing text COPY OUT\n"));
+		libpq_append_conn_error(conn, "PQgetline: not doing text COPY OUT");
 		*s = '\0';
 		return EOF;
 	}
@@ -1862,8 +1848,7 @@ pqEndcopy3(PGconn *conn)
 		conn->asyncStatus != PGASYNC_COPY_OUT &&
 		conn->asyncStatus != PGASYNC_COPY_BOTH)
 	{
-		appendPQExpBufferStr(&conn->errorMessage,
-							 libpq_gettext("no COPY in progress\n"));
+		libpq_append_conn_error(conn, "no COPY in progress");
 		return 1;
 	}
 
@@ -2126,15 +2111,13 @@ pqFunctionCall3(PGconn *conn, Oid fnid,
 						conn->result = PQmakeEmptyPGresult(conn, status);
 						if (!conn->result)
 						{
-							appendPQExpBufferStr(&conn->errorMessage,
-												 libpq_gettext("out of memory\n"));
+							libpq_append_conn_error(conn, "out of memory");
 							pqSaveErrorResult(conn);
 						}
 					}
 					else
 					{
-						appendPQExpBufferStr(&conn->errorMessage,
-											 libpq_gettext("protocol error: no function result\n"));
+						libpq_append_conn_error(conn, "protocol error: no function result");
 						pqSaveErrorResult(conn);
 					}
 				}
@@ -2145,9 +2128,7 @@ pqFunctionCall3(PGconn *conn, Oid fnid,
 				break;
 			default:
 				/* The backend violates the protocol. */
-				appendPQExpBuffer(&conn->errorMessage,
-								  libpq_gettext("protocol error: id=0x%x\n"),
-								  id);
+				libpq_append_conn_error(conn, "protocol error: id=0x%x", id);
 				pqSaveErrorResult(conn);
 				/* trust the specified message length as what to skip */
 				conn->inStart += 5 + msgLength;
diff --git a/src/interfaces/libpq/fe-secure-common.c b/src/interfaces/libpq/fe-secure-common.c
index cc8a2b85938d..a0b2068dc011 100644
--- a/src/interfaces/libpq/fe-secure-common.c
+++ b/src/interfaces/libpq/fe-secure-common.c
@@ -96,8 +96,7 @@ pq_verify_peer_name_matches_certificate_name(PGconn *conn,
 
 	if (!(host && host[0] != '\0'))
 	{
-		appendPQExpBufferStr(&conn->errorMessage,
-							 libpq_gettext("host name must be specified\n"));
+		libpq_append_conn_error(conn, "host name must be specified");
 		return -1;
 	}
 
@@ -108,8 +107,7 @@ pq_verify_peer_name_matches_certificate_name(PGconn *conn,
 	name = malloc(namelen + 1);
 	if (name == NULL)
 	{
-		appendPQExpBufferStr(&conn->errorMessage,
-							 libpq_gettext("out of memory\n"));
+		libpq_append_conn_error(conn, "out of memory");
 		return -1;
 	}
 	memcpy(name, namedata, namelen);
@@ -122,8 +120,7 @@ pq_verify_peer_name_matches_certificate_name(PGconn *conn,
 	if (namelen != strlen(name))
 	{
 		free(name);
-		appendPQExpBufferStr(&conn->errorMessage,
-							 libpq_gettext("SSL certificate's name contains embedded null\n"));
+		libpq_append_conn_error(conn, "SSL certificate's name contains embedded null");
 		return -1;
 	}
 
@@ -173,8 +170,7 @@ pq_verify_peer_name_matches_certificate_ip(PGconn *conn,
 
 	if (!(host && host[0] != '\0'))
 	{
-		appendPQExpBufferStr(&conn->errorMessage,
-							 libpq_gettext("host name must be specified\n"));
+		libpq_append_conn_error(conn, "host name must be specified");
 		return -1;
 	}
 
@@ -229,8 +225,7 @@ pq_verify_peer_name_matches_certificate_ip(PGconn *conn,
 		 * Not IPv4 or IPv6. We could ignore the field, but leniency seems
 		 * wrong given the subject matter.
 		 */
-		appendPQExpBuffer(&conn->errorMessage,
-						  libpq_gettext("certificate contains IP address with invalid length %lu\n"),
+		libpq_append_conn_error(conn, "certificate contains IP address with invalid length %lu",
 						  (unsigned long) iplen);
 		return -1;
 	}
@@ -239,8 +234,7 @@ pq_verify_peer_name_matches_certificate_ip(PGconn *conn,
 	addrstr = pg_inet_net_ntop(family, ipdata, 8 * iplen, tmp, sizeof(tmp));
 	if (!addrstr)
 	{
-		appendPQExpBuffer(&conn->errorMessage,
-						  libpq_gettext("could not convert certificate's IP address to string: %s\n"),
+		libpq_append_conn_error(conn, "could not convert certificate's IP address to string: %s",
 						  strerror_r(errno, sebuf, sizeof(sebuf)));
 		return -1;
 	}
@@ -272,8 +266,7 @@ pq_verify_peer_name_matches_certificate(PGconn *conn)
 	/* Check that we have a hostname to compare with. */
 	if (!(host && host[0] != '\0'))
 	{
-		appendPQExpBufferStr(&conn->errorMessage,
-							 libpq_gettext("host name must be specified for a verified SSL connection\n"));
+		libpq_append_conn_error(conn, "host name must be specified for a verified SSL connection");
 		return false;
 	}
 
@@ -290,21 +283,20 @@ pq_verify_peer_name_matches_certificate(PGconn *conn)
 		if (names_examined > 1)
 		{
 			appendPQExpBuffer(&conn->errorMessage,
-							  libpq_ngettext("server certificate for \"%s\" (and %d other name) does not match host name \"%s\"\n",
-											 "server certificate for \"%s\" (and %d other names) does not match host name \"%s\"\n",
+							  libpq_ngettext("server certificate for \"%s\" (and %d other name) does not match host name \"%s\"",
+											 "server certificate for \"%s\" (and %d other names) does not match host name \"%s\"",
 											 names_examined - 1),
 							  first_name, names_examined - 1, host);
+			appendPQExpBufferChar(&conn->errorMessage, '\n');
 		}
 		else if (names_examined == 1)
 		{
-			appendPQExpBuffer(&conn->errorMessage,
-							  libpq_gettext("server certificate for \"%s\" does not match host name \"%s\"\n"),
+			libpq_append_conn_error(conn, "server certificate for \"%s\" does not match host name \"%s\"",
 							  first_name, host);
 		}
 		else
 		{
-			appendPQExpBufferStr(&conn->errorMessage,
-								 libpq_gettext("could not get server's host name from server certificate\n"));
+			libpq_append_conn_error(conn, "could not get server's host name from server certificate");
 		}
 	}
 
diff --git a/src/interfaces/libpq/fe-secure-gssapi.c b/src/interfaces/libpq/fe-secure-gssapi.c
index dee0982eba20..0ce92dbf4309 100644
--- a/src/interfaces/libpq/fe-secure-gssapi.c
+++ b/src/interfaces/libpq/fe-secure-gssapi.c
@@ -205,16 +205,14 @@ pg_GSS_write(PGconn *conn, const void *ptr, size_t len)
 
 		if (conf_state == 0)
 		{
-			appendPQExpBufferStr(&conn->errorMessage,
-								 libpq_gettext("outgoing GSSAPI message would not use confidentiality\n"));
+			libpq_append_conn_error(conn, "outgoing GSSAPI message would not use confidentiality");
 			errno = EIO;		/* for lack of a better idea */
 			goto cleanup;
 		}
 
 		if (output.length > PQ_GSS_SEND_BUFFER_SIZE - sizeof(uint32))
 		{
-			appendPQExpBuffer(&conn->errorMessage,
-							  libpq_gettext("client tried to send oversize GSSAPI packet (%zu > %zu)\n"),
+			libpq_append_conn_error(conn, "client tried to send oversize GSSAPI packet (%zu > %zu)",
 							  (size_t) output.length,
 							  PQ_GSS_SEND_BUFFER_SIZE - sizeof(uint32));
 			errno = EIO;		/* for lack of a better idea */
@@ -350,8 +348,7 @@ pg_GSS_read(PGconn *conn, void *ptr, size_t len)
 
 		if (input.length > PQ_GSS_RECV_BUFFER_SIZE - sizeof(uint32))
 		{
-			appendPQExpBuffer(&conn->errorMessage,
-							  libpq_gettext("oversize GSSAPI packet sent by the server (%zu > %zu)\n"),
+			libpq_append_conn_error(conn, "oversize GSSAPI packet sent by the server (%zu > %zu)",
 							  (size_t) input.length,
 							  PQ_GSS_RECV_BUFFER_SIZE - sizeof(uint32));
 			errno = EIO;		/* for lack of a better idea */
@@ -399,8 +396,7 @@ pg_GSS_read(PGconn *conn, void *ptr, size_t len)
 
 		if (conf_state == 0)
 		{
-			appendPQExpBufferStr(&conn->errorMessage,
-								 libpq_gettext("incoming GSSAPI message did not use confidentiality\n"));
+			libpq_append_conn_error(conn, "incoming GSSAPI message did not use confidentiality");
 			ret = -1;
 			errno = EIO;		/* for lack of a better idea */
 			goto cleanup;
@@ -500,8 +496,7 @@ pqsecure_open_gss(PGconn *conn)
 		PqGSSResultBuffer = malloc(PQ_GSS_RECV_BUFFER_SIZE);
 		if (!PqGSSSendBuffer || !PqGSSRecvBuffer || !PqGSSResultBuffer)
 		{
-			appendPQExpBufferStr(&conn->errorMessage,
-								 libpq_gettext("out of memory\n"));
+			libpq_append_conn_error(conn, "out of memory");
 			return PGRES_POLLING_FAILED;
 		}
 		PqGSSSendLength = PqGSSSendNext = PqGSSSendConsumed = 0;
@@ -592,8 +587,7 @@ pqsecure_open_gss(PGconn *conn)
 		input.length = pg_ntoh32(*(uint32 *) PqGSSRecvBuffer);
 		if (input.length > PQ_GSS_RECV_BUFFER_SIZE - sizeof(uint32))
 		{
-			appendPQExpBuffer(&conn->errorMessage,
-							  libpq_gettext("oversize GSSAPI packet sent by the server (%zu > %zu)\n"),
+			libpq_append_conn_error(conn, "oversize GSSAPI packet sent by the server (%zu > %zu)",
 							  (size_t) input.length,
 							  PQ_GSS_RECV_BUFFER_SIZE - sizeof(uint32));
 			return PGRES_POLLING_FAILED;
diff --git a/src/interfaces/libpq/fe-secure-openssl.c b/src/interfaces/libpq/fe-secure-openssl.c
index b42a908733ae..bad85359b6c2 100644
--- a/src/interfaces/libpq/fe-secure-openssl.c
+++ b/src/interfaces/libpq/fe-secure-openssl.c
@@ -212,20 +212,17 @@ pgtls_read(PGconn *conn, void *ptr, size_t len)
 				result_errno = SOCK_ERRNO;
 				if (result_errno == EPIPE ||
 					result_errno == ECONNRESET)
-					appendPQExpBufferStr(&conn->errorMessage,
-										 libpq_gettext("server closed the connection unexpectedly\n"
-													   "\tThis probably means the server terminated abnormally\n"
-													   "\tbefore or while processing the request.\n"));
+					libpq_append_conn_error(conn, "server closed the connection unexpectedly\n"
+									   "\tThis probably means the server terminated abnormally\n"
+									   "\tbefore or while processing the request.");
 				else
-					appendPQExpBuffer(&conn->errorMessage,
-									  libpq_gettext("SSL SYSCALL error: %s\n"),
+					libpq_append_conn_error(conn, "SSL SYSCALL error: %s",
 									  SOCK_STRERROR(result_errno,
 													sebuf, sizeof(sebuf)));
 			}
 			else
 			{
-				appendPQExpBufferStr(&conn->errorMessage,
-									 libpq_gettext("SSL SYSCALL error: EOF detected\n"));
+				libpq_append_conn_error(conn, "SSL SYSCALL error: EOF detected");
 				/* assume the connection is broken */
 				result_errno = ECONNRESET;
 				n = -1;
@@ -235,8 +232,7 @@ pgtls_read(PGconn *conn, void *ptr, size_t len)
 			{
 				char	   *errm = SSLerrmessage(ecode);
 
-				appendPQExpBuffer(&conn->errorMessage,
-								  libpq_gettext("SSL error: %s\n"), errm);
+				libpq_append_conn_error(conn, "SSL error: %s", errm);
 				SSLerrfree(errm);
 				/* assume the connection is broken */
 				result_errno = ECONNRESET;
@@ -250,15 +246,12 @@ pgtls_read(PGconn *conn, void *ptr, size_t len)
 			 * a clean connection closure, so we should not report it as a
 			 * server crash.
 			 */
-			appendPQExpBufferStr(&conn->errorMessage,
-								 libpq_gettext("SSL connection has been closed unexpectedly\n"));
+			libpq_append_conn_error(conn, "SSL connection has been closed unexpectedly");
 			result_errno = ECONNRESET;
 			n = -1;
 			break;
 		default:
-			appendPQExpBuffer(&conn->errorMessage,
-							  libpq_gettext("unrecognized SSL error code: %d\n"),
-							  err);
+			libpq_append_conn_error(conn, "unrecognized SSL error code: %d", err);
 			/* assume the connection is broken */
 			result_errno = ECONNRESET;
 			n = -1;
@@ -319,20 +312,17 @@ pgtls_write(PGconn *conn, const void *ptr, size_t len)
 			{
 				result_errno = SOCK_ERRNO;
 				if (result_errno == EPIPE || result_errno == ECONNRESET)
-					appendPQExpBufferStr(&conn->errorMessage,
-										 libpq_gettext("server closed the connection unexpectedly\n"
-													   "\tThis probably means the server terminated abnormally\n"
-													   "\tbefore or while processing the request.\n"));
+					libpq_append_conn_error(conn, "server closed the connection unexpectedly\n"
+									   "\tThis probably means the server terminated abnormally\n"
+									   "\tbefore or while processing the request.");
 				else
-					appendPQExpBuffer(&conn->errorMessage,
-									  libpq_gettext("SSL SYSCALL error: %s\n"),
+					libpq_append_conn_error(conn, "SSL SYSCALL error: %s",
 									  SOCK_STRERROR(result_errno,
 													sebuf, sizeof(sebuf)));
 			}
 			else
 			{
-				appendPQExpBufferStr(&conn->errorMessage,
-									 libpq_gettext("SSL SYSCALL error: EOF detected\n"));
+				libpq_append_conn_error(conn, "SSL SYSCALL error: EOF detected");
 				/* assume the connection is broken */
 				result_errno = ECONNRESET;
 				n = -1;
@@ -342,8 +332,7 @@ pgtls_write(PGconn *conn, const void *ptr, size_t len)
 			{
 				char	   *errm = SSLerrmessage(ecode);
 
-				appendPQExpBuffer(&conn->errorMessage,
-								  libpq_gettext("SSL error: %s\n"), errm);
+				libpq_append_conn_error(conn, "SSL error: %s", errm);
 				SSLerrfree(errm);
 				/* assume the connection is broken */
 				result_errno = ECONNRESET;
@@ -357,15 +346,12 @@ pgtls_write(PGconn *conn, const void *ptr, size_t len)
 			 * a clean connection closure, so we should not report it as a
 			 * server crash.
 			 */
-			appendPQExpBufferStr(&conn->errorMessage,
-								 libpq_gettext("SSL connection has been closed unexpectedly\n"));
+			libpq_append_conn_error(conn, "SSL connection has been closed unexpectedly");
 			result_errno = ECONNRESET;
 			n = -1;
 			break;
 		default:
-			appendPQExpBuffer(&conn->errorMessage,
-							  libpq_gettext("unrecognized SSL error code: %d\n"),
-							  err);
+			libpq_append_conn_error(conn, "unrecognized SSL error code: %d", err);
 			/* assume the connection is broken */
 			result_errno = ECONNRESET;
 			n = -1;
@@ -403,8 +389,7 @@ pgtls_get_peer_certificate_hash(PGconn *conn, size_t *len)
 	if (!OBJ_find_sigid_algs(X509_get_signature_nid(peer_cert),
 							 &algo_nid, NULL))
 	{
-		appendPQExpBufferStr(&conn->errorMessage,
-							 libpq_gettext("could not determine server certificate signature algorithm\n"));
+		libpq_append_conn_error(conn, "could not determine server certificate signature algorithm");
 		return NULL;
 	}
 
@@ -424,8 +409,7 @@ pgtls_get_peer_certificate_hash(PGconn *conn, size_t *len)
 			algo_type = EVP_get_digestbynid(algo_nid);
 			if (algo_type == NULL)
 			{
-				appendPQExpBuffer(&conn->errorMessage,
-								  libpq_gettext("could not find digest for NID %s\n"),
+				libpq_append_conn_error(conn, "could not find digest for NID %s",
 								  OBJ_nid2sn(algo_nid));
 				return NULL;
 			}
@@ -434,8 +418,7 @@ pgtls_get_peer_certificate_hash(PGconn *conn, size_t *len)
 
 	if (!X509_digest(peer_cert, algo_type, hash, &hash_size))
 	{
-		appendPQExpBufferStr(&conn->errorMessage,
-							 libpq_gettext("could not generate peer certificate hash\n"));
+		libpq_append_conn_error(conn, "could not generate peer certificate hash");
 		return NULL;
 	}
 
@@ -443,8 +426,7 @@ pgtls_get_peer_certificate_hash(PGconn *conn, size_t *len)
 	cert_hash = malloc(hash_size);
 	if (cert_hash == NULL)
 	{
-		appendPQExpBufferStr(&conn->errorMessage,
-							 libpq_gettext("out of memory\n"));
+		libpq_append_conn_error(conn, "out of memory");
 		return NULL;
 	}
 	memcpy(cert_hash, hash, hash_size);
@@ -491,8 +473,7 @@ openssl_verify_peer_name_matches_certificate_name(PGconn *conn, ASN1_STRING *nam
 	/* Should not happen... */
 	if (name_entry == NULL)
 	{
-		appendPQExpBufferStr(&conn->errorMessage,
-							 libpq_gettext("SSL certificate's name entry is missing\n"));
+		libpq_append_conn_error(conn, "SSL certificate's name entry is missing");
 		return -1;
 	}
 
@@ -526,8 +507,7 @@ openssl_verify_peer_name_matches_certificate_ip(PGconn *conn,
 	/* Should not happen... */
 	if (addr_entry == NULL)
 	{
-		appendPQExpBufferStr(&conn->errorMessage,
-							 libpq_gettext("SSL certificate's address entry is missing\n"));
+		libpq_append_conn_error(conn, "SSL certificate's address entry is missing");
 		return -1;
 	}
 
@@ -944,9 +924,7 @@ initialize_SSL(PGconn *conn)
 	{
 		char	   *err = SSLerrmessage(ERR_get_error());
 
-		appendPQExpBuffer(&conn->errorMessage,
-						  libpq_gettext("could not create SSL context: %s\n"),
-						  err);
+		libpq_append_conn_error(conn, "could not create SSL context: %s", err);
 		SSLerrfree(err);
 		return -1;
 	}
@@ -983,8 +961,7 @@ initialize_SSL(PGconn *conn)
 
 		if (ssl_min_ver == -1)
 		{
-			appendPQExpBuffer(&conn->errorMessage,
-							  libpq_gettext("invalid value \"%s\" for minimum SSL protocol version\n"),
+			libpq_append_conn_error(conn, "invalid value \"%s\" for minimum SSL protocol version",
 							  conn->ssl_min_protocol_version);
 			SSL_CTX_free(SSL_context);
 			return -1;
@@ -994,9 +971,7 @@ initialize_SSL(PGconn *conn)
 		{
 			char	   *err = SSLerrmessage(ERR_get_error());
 
-			appendPQExpBuffer(&conn->errorMessage,
-							  libpq_gettext("could not set minimum SSL protocol version: %s\n"),
-							  err);
+			libpq_append_conn_error(conn, "could not set minimum SSL protocol version: %s", err);
 			SSLerrfree(err);
 			SSL_CTX_free(SSL_context);
 			return -1;
@@ -1012,8 +987,7 @@ initialize_SSL(PGconn *conn)
 
 		if (ssl_max_ver == -1)
 		{
-			appendPQExpBuffer(&conn->errorMessage,
-							  libpq_gettext("invalid value \"%s\" for maximum SSL protocol version\n"),
+			libpq_append_conn_error(conn, "invalid value \"%s\" for maximum SSL protocol version",
 							  conn->ssl_max_protocol_version);
 			SSL_CTX_free(SSL_context);
 			return -1;
@@ -1023,9 +997,7 @@ initialize_SSL(PGconn *conn)
 		{
 			char	   *err = SSLerrmessage(ERR_get_error());
 
-			appendPQExpBuffer(&conn->errorMessage,
-							  libpq_gettext("could not set maximum SSL protocol version: %s\n"),
-							  err);
+			libpq_append_conn_error(conn, "could not set maximum SSL protocol version: %s", err);
 			SSLerrfree(err);
 			SSL_CTX_free(SSL_context);
 			return -1;
@@ -1059,8 +1031,7 @@ initialize_SSL(PGconn *conn)
 		{
 			char	   *err = SSLerrmessage(ERR_get_error());
 
-			appendPQExpBuffer(&conn->errorMessage,
-							  libpq_gettext("could not read root certificate file \"%s\": %s\n"),
+			libpq_append_conn_error(conn, "could not read root certificate file \"%s\": %s",
 							  fnbuf, err);
 			SSLerrfree(err);
 			SSL_CTX_free(SSL_context);
@@ -1112,13 +1083,11 @@ initialize_SSL(PGconn *conn)
 			 * that it seems worth having a specialized error message for it.
 			 */
 			if (fnbuf[0] == '\0')
-				appendPQExpBuffer(&conn->errorMessage,
-								  libpq_gettext("could not get home directory to locate root certificate file\n"
-												"Either provide the file or change sslmode to disable server certificate verification.\n"));
+				libpq_append_conn_error(conn, "could not get home directory to locate root certificate file\n"
+								   "Either provide the file or change sslmode to disable server certificate verification.");
 			else
-				appendPQExpBuffer(&conn->errorMessage,
-								  libpq_gettext("root certificate file \"%s\" does not exist\n"
-												"Either provide the file or change sslmode to disable server certificate verification.\n"), fnbuf);
+				libpq_append_conn_error(conn, "root certificate file \"%s\" does not exist\n"
+								   "Either provide the file or change sslmode to disable server certificate verification.", fnbuf);
 			SSL_CTX_free(SSL_context);
 			return -1;
 		}
@@ -1147,8 +1116,7 @@ initialize_SSL(PGconn *conn)
 		 */
 		if (errno != ENOENT && errno != ENOTDIR)
 		{
-			appendPQExpBuffer(&conn->errorMessage,
-							  libpq_gettext("could not open certificate file \"%s\": %s\n"),
+			libpq_append_conn_error(conn, "could not open certificate file \"%s\": %s",
 							  fnbuf, strerror_r(errno, sebuf, sizeof(sebuf)));
 			SSL_CTX_free(SSL_context);
 			return -1;
@@ -1166,8 +1134,7 @@ initialize_SSL(PGconn *conn)
 		{
 			char	   *err = SSLerrmessage(ERR_get_error());
 
-			appendPQExpBuffer(&conn->errorMessage,
-							  libpq_gettext("could not read certificate file \"%s\": %s\n"),
+			libpq_append_conn_error(conn, "could not read certificate file \"%s\": %s",
 							  fnbuf, err);
 			SSLerrfree(err);
 			SSL_CTX_free(SSL_context);
@@ -1191,9 +1158,7 @@ initialize_SSL(PGconn *conn)
 	{
 		char	   *err = SSLerrmessage(ERR_get_error());
 
-		appendPQExpBuffer(&conn->errorMessage,
-						  libpq_gettext("could not establish SSL connection: %s\n"),
-						  err);
+		libpq_append_conn_error(conn, "could not establish SSL connection: %s", err);
 		SSLerrfree(err);
 		SSL_CTX_free(SSL_context);
 		return -1;
@@ -1225,9 +1190,7 @@ initialize_SSL(PGconn *conn)
 			{
 				char	   *err = SSLerrmessage(ERR_get_error());
 
-				appendPQExpBuffer(&conn->errorMessage,
-								  libpq_gettext("could not set SSL Server Name Indication (SNI): %s\n"),
-								  err);
+				libpq_append_conn_error(conn, "could not set SSL Server Name Indication (SNI): %s", err);
 				SSLerrfree(err);
 				return -1;
 			}
@@ -1255,8 +1218,7 @@ initialize_SSL(PGconn *conn)
 
 			if (engine_str == NULL)
 			{
-				appendPQExpBufferStr(&conn->errorMessage,
-									 libpq_gettext("out of memory\n"));
+				libpq_append_conn_error(conn, "out of memory");
 				return -1;
 			}
 
@@ -1271,8 +1233,7 @@ initialize_SSL(PGconn *conn)
 			{
 				char	   *err = SSLerrmessage(ERR_get_error());
 
-				appendPQExpBuffer(&conn->errorMessage,
-								  libpq_gettext("could not load SSL engine \"%s\": %s\n"),
+				libpq_append_conn_error(conn, "could not load SSL engine \"%s\": %s",
 								  engine_str, err);
 				SSLerrfree(err);
 				free(engine_str);
@@ -1283,8 +1244,7 @@ initialize_SSL(PGconn *conn)
 			{
 				char	   *err = SSLerrmessage(ERR_get_error());
 
-				appendPQExpBuffer(&conn->errorMessage,
-								  libpq_gettext("could not initialize SSL engine \"%s\": %s\n"),
+				libpq_append_conn_error(conn, "could not initialize SSL engine \"%s\": %s",
 								  engine_str, err);
 				SSLerrfree(err);
 				ENGINE_free(conn->engine);
@@ -1299,8 +1259,7 @@ initialize_SSL(PGconn *conn)
 			{
 				char	   *err = SSLerrmessage(ERR_get_error());
 
-				appendPQExpBuffer(&conn->errorMessage,
-								  libpq_gettext("could not read private SSL key \"%s\" from engine \"%s\": %s\n"),
+				libpq_append_conn_error(conn, "could not read private SSL key \"%s\" from engine \"%s\": %s",
 								  engine_colon, engine_str, err);
 				SSLerrfree(err);
 				ENGINE_finish(conn->engine);
@@ -1313,8 +1272,7 @@ initialize_SSL(PGconn *conn)
 			{
 				char	   *err = SSLerrmessage(ERR_get_error());
 
-				appendPQExpBuffer(&conn->errorMessage,
-								  libpq_gettext("could not load private SSL key \"%s\" from engine \"%s\": %s\n"),
+				libpq_append_conn_error(conn, "could not load private SSL key \"%s\" from engine \"%s\": %s",
 								  engine_colon, engine_str, err);
 				SSLerrfree(err);
 				ENGINE_finish(conn->engine);
@@ -1351,12 +1309,10 @@ initialize_SSL(PGconn *conn)
 		if (stat(fnbuf, &buf) != 0)
 		{
 			if (errno == ENOENT)
-				appendPQExpBuffer(&conn->errorMessage,
-								  libpq_gettext("certificate present, but not private key file \"%s\"\n"),
+				libpq_append_conn_error(conn, "certificate present, but not private key file \"%s\"",
 								  fnbuf);
 			else
-				appendPQExpBuffer(&conn->errorMessage,
-								  libpq_gettext("could not stat private key file \"%s\": %m\n"),
+				libpq_append_conn_error(conn, "could not stat private key file \"%s\": %m",
 								  fnbuf);
 			return -1;
 		}
@@ -1364,8 +1320,7 @@ initialize_SSL(PGconn *conn)
 		/* Key file must be a regular file */
 		if (!S_ISREG(buf.st_mode))
 		{
-			appendPQExpBuffer(&conn->errorMessage,
-							  libpq_gettext("private key file \"%s\" is not a regular file\n"),
+			libpq_append_conn_error(conn, "private key file \"%s\" is not a regular file",
 							  fnbuf);
 			return -1;
 		}
@@ -1397,9 +1352,9 @@ initialize_SSL(PGconn *conn)
 			buf.st_mode & (S_IWGRP | S_IXGRP | S_IRWXO) :
 			buf.st_mode & (S_IRWXG | S_IRWXO))
 		{
-			appendPQExpBuffer(&conn->errorMessage,
-							  libpq_gettext("private key file \"%s\" has group or world access; file must have permissions u=rw (0600) or less if owned by the current user, or permissions u=rw,g=r (0640) or less if owned by root\n"),
-							  fnbuf);
+			libpq_append_conn_error(conn,
+									"private key file \"%s\" has group or world access; file must have permissions u=rw (0600) or less if owned by the current user, or permissions u=rw,g=r (0640) or less if owned by root",
+									fnbuf);
 			return -1;
 		}
 #endif
@@ -1422,8 +1377,7 @@ initialize_SSL(PGconn *conn)
 			 */
 			if (SSL_use_PrivateKey_file(conn->ssl, fnbuf, SSL_FILETYPE_ASN1) != 1)
 			{
-				appendPQExpBuffer(&conn->errorMessage,
-								  libpq_gettext("could not load private key file \"%s\": %s\n"),
+				libpq_append_conn_error(conn, "could not load private key file \"%s\": %s",
 								  fnbuf, err);
 				SSLerrfree(err);
 				return -1;
@@ -1439,8 +1393,7 @@ initialize_SSL(PGconn *conn)
 	{
 		char	   *err = SSLerrmessage(ERR_get_error());
 
-		appendPQExpBuffer(&conn->errorMessage,
-						  libpq_gettext("certificate does not match private key file \"%s\": %s\n"),
+		libpq_append_conn_error(conn, "certificate does not match private key file \"%s\": %s",
 						  fnbuf, err);
 		SSLerrfree(err);
 		return -1;
@@ -1493,12 +1446,10 @@ open_client_SSL(PGconn *conn)
 					char		sebuf[PG_STRERROR_R_BUFLEN];
 
 					if (r == -1)
-						appendPQExpBuffer(&conn->errorMessage,
-										  libpq_gettext("SSL SYSCALL error: %s\n"),
+						libpq_append_conn_error(conn, "SSL SYSCALL error: %s",
 										  SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof(sebuf)));
 					else
-						appendPQExpBufferStr(&conn->errorMessage,
-											 libpq_gettext("SSL SYSCALL error: EOF detected\n"));
+						libpq_append_conn_error(conn, "SSL SYSCALL error: EOF detected");
 					pgtls_close(conn);
 					return PGRES_POLLING_FAILED;
 				}
@@ -1506,9 +1457,7 @@ open_client_SSL(PGconn *conn)
 				{
 					char	   *err = SSLerrmessage(ecode);
 
-					appendPQExpBuffer(&conn->errorMessage,
-									  libpq_gettext("SSL error: %s\n"),
-									  err);
+					libpq_append_conn_error(conn, "SSL error: %s", err);
 					SSLerrfree(err);
 					switch (ERR_GET_REASON(ecode))
 					{
@@ -1539,8 +1488,7 @@ open_client_SSL(PGconn *conn)
 						case SSL_R_VERSION_TOO_HIGH:
 						case SSL_R_VERSION_TOO_LOW:
 #endif
-							appendPQExpBuffer(&conn->errorMessage,
-											  libpq_gettext("This may indicate that the server does not support any SSL protocol version between %s and %s.\n"),
+							libpq_append_conn_error(conn, "This may indicate that the server does not support any SSL protocol version between %s and %s.",
 											  conn->ssl_min_protocol_version ?
 											  conn->ssl_min_protocol_version :
 											  MIN_OPENSSL_TLS_VERSION,
@@ -1556,9 +1504,7 @@ open_client_SSL(PGconn *conn)
 				}
 
 			default:
-				appendPQExpBuffer(&conn->errorMessage,
-								  libpq_gettext("unrecognized SSL error code: %d\n"),
-								  err);
+				libpq_append_conn_error(conn, "unrecognized SSL error code: %d", err);
 				pgtls_close(conn);
 				return PGRES_POLLING_FAILED;
 		}
@@ -1575,9 +1521,7 @@ open_client_SSL(PGconn *conn)
 	{
 		char	   *err = SSLerrmessage(ERR_get_error());
 
-		appendPQExpBuffer(&conn->errorMessage,
-						  libpq_gettext("certificate could not be obtained: %s\n"),
-						  err);
+		libpq_append_conn_error(conn, "certificate could not be obtained: %s", err);
 		SSLerrfree(err);
 		pgtls_close(conn);
 		return PGRES_POLLING_FAILED;
diff --git a/src/interfaces/libpq/fe-secure.c b/src/interfaces/libpq/fe-secure.c
index 3df4a97f2e90..f0e503e98f6a 100644
--- a/src/interfaces/libpq/fe-secure.c
+++ b/src/interfaces/libpq/fe-secure.c
@@ -254,15 +254,13 @@ pqsecure_raw_read(PGconn *conn, void *ptr, size_t len)
 
 			case EPIPE:
 			case ECONNRESET:
-				appendPQExpBufferStr(&conn->errorMessage,
-									 libpq_gettext("server closed the connection unexpectedly\n"
-												   "\tThis probably means the server terminated abnormally\n"
-												   "\tbefore or while processing the request.\n"));
+				libpq_append_conn_error(conn, "server closed the connection unexpectedly\n"
+								   "\tThis probably means the server terminated abnormally\n"
+								   "\tbefore or while processing the request.");
 				break;
 
 			default:
-				appendPQExpBuffer(&conn->errorMessage,
-								  libpq_gettext("could not receive data from server: %s\n"),
+				libpq_append_conn_error(conn, "could not receive data from server: %s",
 								  SOCK_STRERROR(result_errno,
 												sebuf, sizeof(sebuf)));
 				break;
@@ -420,7 +418,8 @@ pqsecure_raw_write(PGconn *conn, const void *ptr, size_t len)
 				snprintf(msgbuf, sizeof(msgbuf),
 						 libpq_gettext("server closed the connection unexpectedly\n"
 									   "\tThis probably means the server terminated abnormally\n"
-									   "\tbefore or while processing the request.\n"));
+									   "\tbefore or while processing the request."));
+				strlcat(msgbuf, "\n", sizeof(msgbuf));
 				conn->write_err_msg = strdup(msgbuf);
 				/* Now claim the write succeeded */
 				n = len;
@@ -431,9 +430,10 @@ pqsecure_raw_write(PGconn *conn, const void *ptr, size_t len)
 				/* Store error message in conn->write_err_msg, if possible */
 				/* (strdup failure is OK, we'll cope later) */
 				snprintf(msgbuf, sizeof(msgbuf),
-						 libpq_gettext("could not send data to server: %s\n"),
+						 libpq_gettext("could not send data to server: %s"),
 						 SOCK_STRERROR(result_errno,
 									   sebuf, sizeof(sebuf)));
+				strlcat(msgbuf, "\n", sizeof(msgbuf));
 				conn->write_err_msg = strdup(msgbuf);
 				/* Now claim the write succeeded */
 				n = len;
-- 
2.37.3

#11Peter Eisentraut
peter.eisentraut@enterprisedb.com
In reply to: Tom Lane (#5)
Re: libpq error message refactoring

On 23.09.22 04:37, Tom Lane wrote:

Separately from that: is it really okay to delegate use of a va_list
argument like that? The other call paths of
appendPQExpBufferVA[_internal] write va_start/va_end directly around it,
not somewhere else in the call chain. I'm too tired to language-lawyer
out what happens when you do it like this, but I'm suspecting that it's
not well-defined portable behavior.

I think what you probably need to do is export appendPQExpBufferVA
as-is and require libpq_append_error to provide the error loop.

There was actually a live problem here, maybe not the exact one you had
in mind: When you actually need the "need more space" loop, you must do
va_end() and va_start() before calling down again. Otherwise, the next
va_arg() gets garbage.

It so happens that the error message

"private key file \"%s\" has group or world access; file must have
permissions u=rw (0600) or less if owned by the current user, or
permissions u=rw,g=r (0640) or less if owned by root"

together with an in-tree test location for the file in question just
barely exceeds INITIAL_EXPBUFFER_SIZE (256), and so my previous patch
would fail the "ssl" test suite. Good test coverage. :)

Anyway, I have updated my patch with your suggestion, which should fix
these kinds of issues.

#12Alvaro Herrera
alvherre@alvh.no-ip.org
In reply to: Peter Eisentraut (#10)
Re: libpq error message refactoring

Hello

I gave this series a quick look. Overall it seems a good idea, since
the issue of newlines-or-not is quite bothersome for the libpq
translations.

+/*
+ * Append a formatted string to the given buffer, after translation.  A
+ * newline is automatically appended; the format should not end with a
+ * newline.
+ */

I find the "after translation" bit unclear -- does it mean that the
caller should have already translated, or is it the other way around? I
would say "Append a formatted string to the given buffer, after
translating it", which (to me) conveys more clearly that translation
occurs here.

+	/* Loop in case we have to retry after enlarging the buffer. */
+	do
+	{
+		errno = save_errno;
+		va_start(args, fmt);
+		done = appendPQExpBufferVA(errorMessage, libpq_gettext(fmt), args);

I wonder if it makes sense to do libpq_gettext() just once, instead of
redoing it on each iteration.

+void
+libpq_append_conn_error(PGconn *conn, const char *fmt, ...)

These two routines are essentially identical. While we could argue
about sharing an underlying implementation, I think it's okay the way
you have it, because the overheard of sharing it would make that
pointless, given how short they are.

+extern void libpq_append_error(PQExpBuffer errorMessage, const char *fmt, ...) pg_attribute_printf(2, 3);
+extern void libpq_append_conn_error(PGconn *conn, const char *fmt, ...) pg_attribute_printf(2, 3);

pg_attribute_printf marker present -- check.

-GETTEXT_TRIGGERS = libpq_gettext pqInternalNotice:2
-GETTEXT_FLAGS    = libpq_gettext:1:pass-c-format pqInternalNotice:2:c-format
+GETTEXT_TRIGGERS = libpq_append_conn_error:2 \
+                   libpq_append_error:2 \
+                   libpq_gettext pqInternalNotice:2
+GETTEXT_FLAGS    = libpq_append_conn_error:2:c-format \
+                   libpq_append_error:2:c-format \
+                   libpq_gettext:1:pass-c-format pqInternalNotice:2:c-format

Looks good.

--- a/src/interfaces/libpq/pqexpbuffer.h
+++ b/src/interfaces/libpq/pqexpbuffer.h
+/*------------------------
+ * appendPQExpBufferVA
+ * Shared guts of printfPQExpBuffer/appendPQExpBuffer.
+ * Attempt to format data and append it to str.  Returns true if done
+ * (either successful or hard failure), false if need to retry.
+ *
+ * Caution: callers must be sure to preserve their entry-time errno
+ * when looping, in case the fmt contains "%m".
+ */
+extern bool appendPQExpBufferVA(PQExpBuffer str, const char *fmt, va_list args) pg_attribute_printf(2, 0);

As an API user, I don't care that this is shared guts for something
else, I just care about what it does. I think deleting that line is a
sufficient fix.

-			appendPQExpBufferStr(&conn->errorMessage,
-								 libpq_gettext("malformed SCRAM message (empty message)\n"));
+			libpq_append_conn_error(conn, "malformed SCRAM message (empty message)");

Overall, this type of change looks positive. I didn't review all these
changes too closely other than the first couple of dozens, as there are
way too many; I suppose you did these with some Emacs macros or something?

@@ -420,7 +418,8 @@ pqsecure_raw_write(PGconn *conn, const void *ptr, size_t len)
snprintf(msgbuf, sizeof(msgbuf),
libpq_gettext("server closed the connection unexpectedly\n"
"\tThis probably means the server terminated abnormally\n"
-									   "\tbefore or while processing the request.\n"));
+									   "\tbefore or while processing the request."));
+				strlcat(msgbuf, "\n", sizeof(msgbuf));
conn->write_err_msg = strdup(msgbuf);
/* Now claim the write succeeded */
n = len;

In these two places, we're writing the error message manually to a
separate variable, so the extra \n is necessary. It looks a bit odd to
do it with strlcat() after the fact, but AFAICT it's necessary as it
keeps the \n out of the translation catalog, which is good. This is
nonobvious, so perhaps add a comment about it.

Thanks

--
Álvaro Herrera PostgreSQL Developer — https://www.EnterpriseDB.com/
"Before you were born your parents weren't as boring as they are now. They
got that way paying your bills, cleaning up your room and listening to you
tell them how idealistic you are." -- Charles J. Sykes' advice to teenagers

#13Peter Eisentraut
peter.eisentraut@enterprisedb.com
In reply to: Alvaro Herrera (#12)
Re: libpq error message refactoring

On 09.11.22 13:29, Alvaro Herrera wrote:

+/*
+ * Append a formatted string to the given buffer, after translation.  A
+ * newline is automatically appended; the format should not end with a
+ * newline.
+ */

I find the "after translation" bit unclear -- does it mean that the
caller should have already translated, or is it the other way around? I
would say "Append a formatted string to the given buffer, after
translating it", which (to me) conveys more clearly that translation
occurs here.

ok

+	/* Loop in case we have to retry after enlarging the buffer. */
+	do
+	{
+		errno = save_errno;
+		va_start(args, fmt);
+		done = appendPQExpBufferVA(errorMessage, libpq_gettext(fmt), args);

I wonder if it makes sense to do libpq_gettext() just once, instead of
redoing it on each iteration.

I wonder whether that would expose us to potential compiler warnings
about the format string not being constant. As long as the compiler can
trace that the string comes from gettext, it knows what's going on.

Also, most error strings in practice don't need the loop, so maybe it's
not a big issue.

+/*------------------------
+ * appendPQExpBufferVA
+ * Shared guts of printfPQExpBuffer/appendPQExpBuffer.
+ * Attempt to format data and append it to str.  Returns true if done
+ * (either successful or hard failure), false if need to retry.
+ *
+ * Caution: callers must be sure to preserve their entry-time errno
+ * when looping, in case the fmt contains "%m".
+ */
+extern bool appendPQExpBufferVA(PQExpBuffer str, const char *fmt, va_list args) pg_attribute_printf(2, 0);

As an API user, I don't care that this is shared guts for something
else, I just care about what it does. I think deleting that line is a
sufficient fix.

ok

@@ -420,7 +418,8 @@ pqsecure_raw_write(PGconn *conn, const void *ptr, size_t len)
snprintf(msgbuf, sizeof(msgbuf),
libpq_gettext("server closed the connection unexpectedly\n"
"\tThis probably means the server terminated abnormally\n"
-									   "\tbefore or while processing the request.\n"));
+									   "\tbefore or while processing the request."));
+				strlcat(msgbuf, "\n", sizeof(msgbuf));
conn->write_err_msg = strdup(msgbuf);
/* Now claim the write succeeded */
n = len;

In these two places, we're writing the error message manually to a
separate variable, so the extra \n is necessary. It looks a bit odd to
do it with strlcat() after the fact, but AFAICT it's necessary as it
keeps the \n out of the translation catalog, which is good. This is
nonobvious, so perhaps add a comment about it.

ok

#14Alvaro Herrera
alvherre@alvh.no-ip.org
In reply to: Peter Eisentraut (#13)
Re: libpq error message refactoring

On 2022-Nov-13, Peter Eisentraut wrote:

On 09.11.22 13:29, Alvaro Herrera wrote:

+	/* Loop in case we have to retry after enlarging the buffer. */
+	do
+	{
+		errno = save_errno;
+		va_start(args, fmt);
+		done = appendPQExpBufferVA(errorMessage, libpq_gettext(fmt), args);

I wonder if it makes sense to do libpq_gettext() just once, instead of
redoing it on each iteration.

I wonder whether that would expose us to potential compiler warnings about
the format string not being constant. As long as the compiler can trace
that the string comes from gettext, it knows what's going on.

Also, most error strings in practice don't need the loop, so maybe it's not
a big issue.

True.

--
Álvaro Herrera Breisgau, Deutschland — https://www.EnterpriseDB.com/