diff --git a/src/backend/libpq/pqformat.c b/src/backend/libpq/pqformat.c
index dc7eb96a72187b4651bc4fdbaddf95de9d7819d7..83d7a2bbf76a0717c90eea0ea1e5b35a202a82ab 100644
--- a/src/backend/libpq/pqformat.c
+++ b/src/backend/libpq/pqformat.c
@@ -24,7 +24,7 @@
  * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- *	$PostgreSQL: pgsql/src/backend/libpq/pqformat.c,v 1.48 2009/01/01 17:23:42 momjian Exp $
+ *	$PostgreSQL: pgsql/src/backend/libpq/pqformat.c,v 1.49 2009/03/02 21:18:43 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -41,6 +41,7 @@
  *		pq_sendcountedtext - append a counted text string (with character set conversion)
  *		pq_sendtext		- append a text string (with conversion)
  *		pq_sendstring	- append a null-terminated text string (with conversion)
+ *		pq_send_ascii_string - append a null-terminated text string (without conversion)
  *		pq_endmessage	- send the completed message to the frontend
  * Note: it is also possible to append data to the StringInfo buffer using
  * the regular StringInfo routines, but this is discouraged since required
@@ -184,7 +185,6 @@ void
 pq_sendstring(StringInfo buf, const char *str)
 {
 	int			slen = strlen(str);
-
 	char	   *p;
 
 	p = pg_server_to_client(str, slen);
@@ -198,6 +198,35 @@ pq_sendstring(StringInfo buf, const char *str)
 		appendBinaryStringInfo(buf, str, slen + 1);
 }
 
+/* --------------------------------
+ *		pq_send_ascii_string	- append a null-terminated text string (without conversion)
+ *
+ * This function intentionally bypasses encoding conversion, instead just
+ * silently replacing any non-7-bit-ASCII characters with question marks.
+ * It is used only when we are having trouble sending an error message to
+ * the client with normal localization and encoding conversion.  The caller
+ * should already have taken measures to ensure the string is just ASCII;
+ * the extra work here is just to make certain we don't send a badly encoded
+ * string to the client (which might or might not be robust about that).
+ *
+ * NB: passed text string must be null-terminated, and so is the data
+ * sent to the frontend.
+ * --------------------------------
+ */
+void
+pq_send_ascii_string(StringInfo buf, const char *str)
+{
+	while (*str)
+	{
+		char	ch = *str++;
+
+		if (IS_HIGHBIT_SET(ch))
+			ch = '?';
+		appendStringInfoCharMacro(buf, ch);
+	}
+	appendStringInfoChar(buf, '\0');
+}
+
 /* --------------------------------
  *		pq_sendint		- append a binary integer to a StringInfo buffer
  * --------------------------------
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 67f8c24b490819d18241707babfe72d8ec5c5180..0439c4c1d1821b35aed6c6a154671fe8c1d8e5a7 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -42,7 +42,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/utils/error/elog.c,v 1.212 2009/01/19 15:34:23 mha Exp $
+ *	  $PostgreSQL: pgsql/src/backend/utils/error/elog.c,v 1.213 2009/03/02 21:18:43 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -72,6 +72,9 @@
 #include "utils/ps_status.h"
 
 
+#undef _
+#define _(x) err_gettext(x)
+
 /* Global variables */
 ErrorContextCallback *error_context_stack = NULL;
 
@@ -164,6 +167,25 @@ in_error_recursion_trouble(void)
 	return (recursion_depth > 2);
 }
 
+/*
+ * One of those fallback steps is to stop trying to localize the error
+ * message, since there's a significant probability that that's exactly
+ * what's causing the recursion.
+ */
+static inline const char *
+err_gettext(const char *str)
+{
+#ifdef ENABLE_NLS
+	if (in_error_recursion_trouble())
+		return str;
+	else
+		return gettext(str);
+#else
+	return str;
+#endif
+}
+
+
 /*
  * errstart --- begin an error-reporting cycle
  *
@@ -631,7 +653,7 @@ errcode_for_socket_access(void)
 		char		   *fmtbuf; \
 		StringInfoData	buf; \
 		/* Internationalize the error format string */ \
-		if (translateit) \
+		if (translateit && !in_error_recursion_trouble()) \
 			fmt = dgettext(edata->domain, fmt); \
 		/* Expand %m in format string */ \
 		fmtbuf = expand_fmt_string(fmt, edata); \
@@ -2137,7 +2159,7 @@ send_message_to_server_log(ErrorData *edata)
 		}
 		else
 		{
-			char	   *msg = _("Not safe to send CSV data\n");
+			const char *msg = _("Not safe to send CSV data\n");
 
 			write(fileno(stderr), msg, strlen(msg));
 			if (!(Log_destination & LOG_DESTINATION_STDERR) &&
@@ -2189,6 +2211,26 @@ write_pipe_chunks(char *data, int len, int dest)
 }
 
 
+/*
+ * Append a text string to the error report being built for the client.
+ *
+ * This is ordinarily identical to pq_sendstring(), but if we are in
+ * error recursion trouble we skip encoding conversion, because of the
+ * possibility that the problem is a failure in the encoding conversion
+ * subsystem itself.  Code elsewhere should ensure that the passed-in
+ * strings will be plain 7-bit ASCII, and thus not in need of conversion,
+ * in such cases.  (In particular, we disable localization of error messages
+ * to help ensure that's true.)
+ */
+static void
+err_sendstring(StringInfo buf, const char *str)
+{
+	if (in_error_recursion_trouble())
+		pq_send_ascii_string(buf, str);
+	else
+		pq_sendstring(buf, str);
+}
+
 /*
  * Write error report to client
  */
@@ -2208,7 +2250,7 @@ send_message_to_frontend(ErrorData *edata)
 		int			i;
 
 		pq_sendbyte(&msgbuf, PG_DIAG_SEVERITY);
-		pq_sendstring(&msgbuf, error_severity(edata->elevel));
+		err_sendstring(&msgbuf, error_severity(edata->elevel));
 
 		/* unpack MAKE_SQLSTATE code */
 		ssval = edata->sqlerrcode;
@@ -2220,19 +2262,19 @@ send_message_to_frontend(ErrorData *edata)
 		tbuf[i] = '\0';
 
 		pq_sendbyte(&msgbuf, PG_DIAG_SQLSTATE);
-		pq_sendstring(&msgbuf, tbuf);
+		err_sendstring(&msgbuf, tbuf);
 
 		/* M field is required per protocol, so always send something */
 		pq_sendbyte(&msgbuf, PG_DIAG_MESSAGE_PRIMARY);
 		if (edata->message)
-			pq_sendstring(&msgbuf, edata->message);
+			err_sendstring(&msgbuf, edata->message);
 		else
-			pq_sendstring(&msgbuf, _("missing error text"));
+			err_sendstring(&msgbuf, _("missing error text"));
 
 		if (edata->detail)
 		{
 			pq_sendbyte(&msgbuf, PG_DIAG_MESSAGE_DETAIL);
-			pq_sendstring(&msgbuf, edata->detail);
+			err_sendstring(&msgbuf, edata->detail);
 		}
 
 		/* detail_log is intentionally not used here */
@@ -2240,52 +2282,52 @@ send_message_to_frontend(ErrorData *edata)
 		if (edata->hint)
 		{
 			pq_sendbyte(&msgbuf, PG_DIAG_MESSAGE_HINT);
-			pq_sendstring(&msgbuf, edata->hint);
+			err_sendstring(&msgbuf, edata->hint);
 		}
 
 		if (edata->context)
 		{
 			pq_sendbyte(&msgbuf, PG_DIAG_CONTEXT);
-			pq_sendstring(&msgbuf, edata->context);
+			err_sendstring(&msgbuf, edata->context);
 		}
 
 		if (edata->cursorpos > 0)
 		{
 			snprintf(tbuf, sizeof(tbuf), "%d", edata->cursorpos);
 			pq_sendbyte(&msgbuf, PG_DIAG_STATEMENT_POSITION);
-			pq_sendstring(&msgbuf, tbuf);
+			err_sendstring(&msgbuf, tbuf);
 		}
 
 		if (edata->internalpos > 0)
 		{
 			snprintf(tbuf, sizeof(tbuf), "%d", edata->internalpos);
 			pq_sendbyte(&msgbuf, PG_DIAG_INTERNAL_POSITION);
-			pq_sendstring(&msgbuf, tbuf);
+			err_sendstring(&msgbuf, tbuf);
 		}
 
 		if (edata->internalquery)
 		{
 			pq_sendbyte(&msgbuf, PG_DIAG_INTERNAL_QUERY);
-			pq_sendstring(&msgbuf, edata->internalquery);
+			err_sendstring(&msgbuf, edata->internalquery);
 		}
 
 		if (edata->filename)
 		{
 			pq_sendbyte(&msgbuf, PG_DIAG_SOURCE_FILE);
-			pq_sendstring(&msgbuf, edata->filename);
+			err_sendstring(&msgbuf, edata->filename);
 		}
 
 		if (edata->lineno > 0)
 		{
 			snprintf(tbuf, sizeof(tbuf), "%d", edata->lineno);
 			pq_sendbyte(&msgbuf, PG_DIAG_SOURCE_LINE);
-			pq_sendstring(&msgbuf, tbuf);
+			err_sendstring(&msgbuf, tbuf);
 		}
 
 		if (edata->funcname)
 		{
 			pq_sendbyte(&msgbuf, PG_DIAG_SOURCE_FUNCTION);
-			pq_sendstring(&msgbuf, edata->funcname);
+			err_sendstring(&msgbuf, edata->funcname);
 		}
 
 		pq_sendbyte(&msgbuf, '\0');		/* terminator */
@@ -2316,7 +2358,7 @@ send_message_to_frontend(ErrorData *edata)
 
 		appendStringInfoChar(&buf, '\n');
 
-		pq_sendstring(&msgbuf, buf.data);
+		err_sendstring(&msgbuf, buf.data);
 
 		pfree(buf.data);
 	}
@@ -2430,10 +2472,6 @@ useful_strerror(int errnum)
 
 /*
  * error_severity --- get localized string representing elevel
- *
- * Note: in an error recursion situation, we stop localizing the tags
- * for ERROR and above.  This is necessary because the problem might be
- * failure to convert one of these strings to the client encoding.
  */
 static const char *
 error_severity(int elevel)
@@ -2463,22 +2501,13 @@ error_severity(int elevel)
 			prefix = _("WARNING");
 			break;
 		case ERROR:
-			if (in_error_recursion_trouble())
-				prefix = "ERROR";
-			else
-				prefix = _("ERROR");
+			prefix = _("ERROR");
 			break;
 		case FATAL:
-			if (in_error_recursion_trouble())
-				prefix = "FATAL";
-			else
-				prefix = _("FATAL");
+			prefix = _("FATAL");
 			break;
 		case PANIC:
-			if (in_error_recursion_trouble())
-				prefix = "PANIC";
-			else
-				prefix = _("PANIC");
+			prefix = _("PANIC");
 			break;
 		default:
 			prefix = "???";
diff --git a/src/backend/utils/mb/wchar.c b/src/backend/utils/mb/wchar.c
index 7b7ebf6bbf84a7195f468ea3ed2c86cdefb9f813..8da81b9f2217c87d1fec158314e6f36f222116de 100644
--- a/src/backend/utils/mb/wchar.c
+++ b/src/backend/utils/mb/wchar.c
@@ -1,7 +1,7 @@
 /*
  * conversion functions between pg_wchar and multibyte streams.
  * Tatsuo Ishii
- * $PostgreSQL: pgsql/src/backend/utils/mb/wchar.c,v 1.71 2009/02/10 19:29:39 petere Exp $
+ * $PostgreSQL: pgsql/src/backend/utils/mb/wchar.c,v 1.72 2009/03/02 21:18:43 tgl Exp $
  *
  */
 /* can be used in either frontend or backend */
@@ -1636,25 +1636,12 @@ report_untranslatable_char(int src_encoding, int dest_encoding,
 	for (j = 0; j < jlimit; j++)
 		p += sprintf(p, "%02x", (unsigned char) mbstr[j]);
 
-	/*
-	 * In an error recursion situation, don't try to translate the message.
-	 * This gets us out of trouble if the problem is failure to convert
-	 * this very message (after translation) to the client encoding.
-	 */
-	if (in_error_recursion_trouble())
-		ereport(ERROR,
-				(errcode(ERRCODE_UNTRANSLATABLE_CHARACTER),
-				 errmsg_internal("character 0x%s of encoding \"%s\" has no equivalent in \"%s\"",
-								 buf,
-								 pg_enc2name_tbl[src_encoding].name,
-								 pg_enc2name_tbl[dest_encoding].name)));
-	else
-		ereport(ERROR,
-				(errcode(ERRCODE_UNTRANSLATABLE_CHARACTER),
-				 errmsg("character 0x%s of encoding \"%s\" has no equivalent in \"%s\"",
-						buf,
-						pg_enc2name_tbl[src_encoding].name,
-						pg_enc2name_tbl[dest_encoding].name)));
+	ereport(ERROR,
+			(errcode(ERRCODE_UNTRANSLATABLE_CHARACTER),
+			 errmsg("character 0x%s of encoding \"%s\" has no equivalent in \"%s\"",
+					buf,
+					pg_enc2name_tbl[src_encoding].name,
+					pg_enc2name_tbl[dest_encoding].name)));
 }
 
 #endif
diff --git a/src/include/libpq/pqformat.h b/src/include/libpq/pqformat.h
index 6962b4eb97c0af243c45b47dd46ecf1a82f63b5f..61692487a9b34724fd21561636ebc829c063a01f 100644
--- a/src/include/libpq/pqformat.h
+++ b/src/include/libpq/pqformat.h
@@ -6,7 +6,7 @@
  * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/libpq/pqformat.h,v 1.27 2009/01/01 17:23:59 momjian Exp $
+ * $PostgreSQL: pgsql/src/include/libpq/pqformat.h,v 1.28 2009/03/02 21:18:43 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -22,6 +22,7 @@ extern void pq_sendcountedtext(StringInfo buf, const char *str, int slen,
 				   bool countincludesself);
 extern void pq_sendtext(StringInfo buf, const char *str, int slen);
 extern void pq_sendstring(StringInfo buf, const char *str);
+extern void pq_send_ascii_string(StringInfo buf, const char *str);
 extern void pq_sendint(StringInfo buf, int i, int b);
 extern void pq_sendint64(StringInfo buf, int64 i);
 extern void pq_sendfloat4(StringInfo buf, float4 f);