Removing support for COPY FROM STDIN in protocol version 2

Started by Heikki Linnakangasalmost 5 years ago22 messages
#1Heikki Linnakangas
hlinnaka@iki.fi
1 attachment(s)

Hi,

The server still supports the old protocol version 2. Protocol version 3
was introduced in PostgreSQL 7.4, so there shouldn't be many clients
around anymore that don't support it.

COPY FROM STDIN is particularly problematic with the old protocol,
because the end-of-copy can only be detected by the \. marker. So the
server has to read the input one byte at a time, and check for \. as it
goes. At [1]/messages/by-id/e7861509-3960-538a-9025-b75a61188e01@iki.fi, I'm working on a patch to change the way the encoding
conversion is performed in COPY FROM, so that we convert the data in
larger chunks, before scanning the input for line boundaries. We can't
do that safely in the old protocol.

I propose that we remove server support for COPY FROM STDIN with
protocol version 2, per attached patch. Even if we could still support
it, it would be a very rarely used and tested codepath, prone to bugs.
Perhaps we could remove support for the old protocol altogether, but I'm
not proposing that we go that far just yet.

[1]: /messages/by-id/e7861509-3960-538a-9025-b75a61188e01@iki.fi
/messages/by-id/e7861509-3960-538a-9025-b75a61188e01@iki.fi

- Heikki

Attachments:

0001-Remove-support-for-COPY-FROM-with-protocol-version-2.patchtext/x-patch; charset=UTF-8; name=0001-Remove-support-for-COPY-FROM-with-protocol-version-2.patchDownload
From 21ce10dd2bf43c6924645bff4d40d18663835dcf Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <heikki.linnakangas@iki.fi>
Date: Wed, 3 Feb 2021 17:40:47 +0200
Subject: [PATCH 1/1] Remove support for COPY FROM with protocol version 2.

I'm working on a patch to refactor the way the encoding conversion is
performed, so that we convert the data in larger chunks, before scanning
the input for line boundaries. We can't do that, if we cannot safely try
to read ahead data past the end-of-copy marker. With the old protocol
gone, we can safely read as much as we want.
---
 src/backend/commands/copyfrom.c          |   7 -
 src/backend/commands/copyfromparse.c     | 162 +++++++----------------
 src/backend/commands/copyto.c            |   2 +-
 src/include/commands/copyfrom_internal.h |   5 +-
 4 files changed, 50 insertions(+), 126 deletions(-)

diff --git a/src/backend/commands/copyfrom.c b/src/backend/commands/copyfrom.c
index c39cc736ed2..6d43d056cca 100644
--- a/src/backend/commands/copyfrom.c
+++ b/src/backend/commands/copyfrom.c
@@ -1125,13 +1125,6 @@ CopyFrom(CopyFromState cstate)
 
 	MemoryContextSwitchTo(oldcontext);
 
-	/*
-	 * In the old protocol, tell pqcomm that we can process normal protocol
-	 * messages again.
-	 */
-	if (cstate->copy_src == COPY_OLD_FE)
-		pq_endmsgread();
-
 	/* Execute AFTER STATEMENT insertion triggers */
 	ExecASInsertTriggers(estate, target_resultRelInfo, cstate->transition_capture);
 
diff --git a/src/backend/commands/copyfromparse.c b/src/backend/commands/copyfromparse.c
index 4c74067f849..e8497cbdf00 100644
--- a/src/backend/commands/copyfromparse.c
+++ b/src/backend/commands/copyfromparse.c
@@ -46,21 +46,6 @@
  * empty statements.  See http://www.cit.gu.edu.au/~anthony/info/C/C.macros.
  */
 
-/*
- * This keeps the character read at the top of the loop in the buffer
- * even if there is more than one read-ahead.
- */
-#define IF_NEED_REFILL_AND_NOT_EOF_CONTINUE(extralen) \
-if (1) \
-{ \
-	if (raw_buf_ptr + (extralen) >= copy_buf_len && !hit_eof) \
-	{ \
-		raw_buf_ptr = prev_raw_ptr; /* undo fetch */ \
-		need_data = true; \
-		continue; \
-	} \
-} else ((void) 0)
-
 /* This consumes the remainder of the buffer and breaks */
 #define IF_NEED_REFILL_AND_EOF_BREAK(extralen) \
 if (1) \
@@ -118,7 +103,7 @@ static int	CopyGetData(CopyFromState cstate, void *databuf,
 						int minread, int maxread);
 static inline bool CopyGetInt32(CopyFromState cstate, int32 *val);
 static inline bool CopyGetInt16(CopyFromState cstate, int16 *val);
-static bool CopyLoadRawBuf(CopyFromState cstate);
+static bool CopyLoadRawBuf(CopyFromState cstate, int minread);
 static int	CopyReadBinaryData(CopyFromState cstate, char *dest, int nbytes);
 
 void
@@ -144,14 +129,9 @@ ReceiveCopyBegin(CopyFromState cstate)
 	else
 	{
 		/* old way */
-		if (cstate->opts.binary)
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("COPY BINARY is not supported to stdout or from stdin")));
-		pq_putemptymessage('G');
-		/* any error in old protocol will make us lose sync */
-		pq_startmsgread();
-		cstate->copy_src = COPY_OLD_FE;
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("COPY FROM STDIN is not supported in protocol version 2")));
 	}
 	/* We *must* flush here to ensure FE knows it can send. */
 	pq_flush();
@@ -225,27 +205,9 @@ CopyGetData(CopyFromState cstate, void *databuf, int minread, int maxread)
 				ereport(ERROR,
 						(errcode_for_file_access(),
 						 errmsg("could not read from COPY file: %m")));
-			if (bytesread == 0)
+			if (bytesread < maxread)
 				cstate->reached_eof = true;
 			break;
-		case COPY_OLD_FE:
-
-			/*
-			 * We cannot read more than minread bytes (which in practice is 1)
-			 * because old protocol doesn't have any clear way of separating
-			 * the COPY stream from following data.  This is slow, but not any
-			 * slower than the code path was originally, and we don't care
-			 * much anymore about the performance of old protocol.
-			 */
-			if (pq_getbytes((char *) databuf, minread))
-			{
-				/* Only a \. terminator is legal EOF in old protocol */
-				ereport(ERROR,
-						(errcode(ERRCODE_CONNECTION_FAILURE),
-						 errmsg("unexpected EOF on client connection with an open transaction")));
-			}
-			bytesread = minread;
-			break;
 		case COPY_NEW_FE:
 			while (maxread > 0 && bytesread < minread && !cstate->reached_eof)
 			{
@@ -312,6 +274,8 @@ CopyGetData(CopyFromState cstate, void *databuf, int minread, int maxread)
 			break;
 		case COPY_CALLBACK:
 			bytesread = cstate->data_source_cb(databuf, minread, maxread);
+			if (bytesread < minread)
+				cstate->reached_eof = true;
 			break;
 	}
 
@@ -363,14 +327,13 @@ CopyGetInt16(CopyFromState cstate, int16 *val)
 /*
  * CopyLoadRawBuf loads some more data into raw_buf
  *
- * Returns true if able to obtain at least one more byte, else false.
+ * Returns true if able to obtain at least 'minread' bytes, else false.
  *
  * If RAW_BUF_BYTES(cstate) > 0, the unprocessed bytes are moved to the start
- * of the buffer and then we load more data after that.  This case occurs only
- * when a multibyte character crosses a bufferload boundary.
+ * of the buffer and then we load more data after that.
  */
 static bool
-CopyLoadRawBuf(CopyFromState cstate)
+CopyLoadRawBuf(CopyFromState cstate, int minread)
 {
 	int			nbytes = RAW_BUF_BYTES(cstate);
 	int			inbytes;
@@ -381,14 +344,15 @@ CopyLoadRawBuf(CopyFromState cstate)
 				nbytes);
 
 	inbytes = CopyGetData(cstate, cstate->raw_buf + nbytes,
-						  1, RAW_BUF_SIZE - nbytes);
+						  minread, RAW_BUF_SIZE - nbytes);
 	nbytes += inbytes;
 	cstate->raw_buf[nbytes] = '\0';
 	cstate->raw_buf_index = 0;
 	cstate->raw_buf_len = nbytes;
 	cstate->bytes_processed += nbytes;
 	pgstat_progress_update_param(PROGRESS_COPY_BYTES_PROCESSED, cstate->bytes_processed);
-	return (inbytes > 0);
+
+	return (inbytes >= minread);
 }
 
 /*
@@ -423,7 +387,7 @@ CopyReadBinaryData(CopyFromState cstate, char *dest, int nbytes)
 			/* Load more data if buffer is empty. */
 			if (RAW_BUF_BYTES(cstate) == 0)
 			{
-				if (!CopyLoadRawBuf(cstate))
+				if (!CopyLoadRawBuf(cstate, 1))
 					break;		/* EOF */
 			}
 
@@ -619,21 +583,17 @@ NextCopyFrom(CopyFromState cstate, ExprContext *econtext,
 		if (fld_count == -1)
 		{
 			/*
-			 * Received EOF marker.  In a V3-protocol copy, wait for the
-			 * protocol-level EOF, and complain if it doesn't come
-			 * immediately.  This ensures that we correctly handle CopyFail,
-			 * if client chooses to send that now.
+			 * Received EOF marker.  Wait for the protocol-level EOF, and
+			 * complain if it doesn't come immediately.  This ensures that we
+			 * correctly handle CopyFail, if client chooses to send that now.
 			 *
-			 * Note that we MUST NOT try to read more data in an old-protocol
-			 * copy, since there is no protocol-level EOF marker then.  We
-			 * could go either way for copy from file, but choose to throw
-			 * error if there's data after the EOF marker, for consistency
-			 * with the new-protocol case.
+			 * When copying from file, we could continue reading like we do in
+			 * text mode, but we choose to throw error if there's data after
+			 * the EOF marker, for consistency with the V3-protocol case.
 			 */
 			char		dummy;
 
-			if (cstate->copy_src != COPY_OLD_FE &&
-				CopyReadBinaryData(cstate, &dummy, 1) > 0)
+			if (CopyReadBinaryData(cstate, &dummy, 1) > 0)
 				ereport(ERROR,
 						(errcode(ERRCODE_BAD_COPY_FILE_FORMAT),
 						 errmsg("received copy data after EOF marker")));
@@ -717,7 +677,7 @@ CopyReadLine(CopyFromState cstate)
 			do
 			{
 				cstate->raw_buf_index = cstate->raw_buf_len;
-			} while (CopyLoadRawBuf(cstate));
+			} while (CopyLoadRawBuf(cstate, 1));
 		}
 	}
 	else
@@ -786,7 +746,6 @@ CopyReadLineText(CopyFromState cstate)
 	char	   *copy_raw_buf;
 	int			raw_buf_ptr;
 	int			copy_buf_len;
-	bool		need_data = false;
 	bool		hit_eof = false;
 	bool		result = false;
 	char		mblen_str[2];
@@ -840,38 +799,41 @@ CopyReadLineText(CopyFromState cstate)
 		char		c;
 
 		/*
-		 * Load more data if needed.  Ideally we would just force four bytes
-		 * of read-ahead and avoid the many calls to
-		 * IF_NEED_REFILL_AND_NOT_EOF_CONTINUE(), but the COPY_OLD_FE protocol
-		 * does not allow us to read too far ahead or we might read into the
-		 * next data, so we read-ahead only as far we know we can.  One
-		 * optimization would be to read-ahead four byte here if
-		 * cstate->copy_src != COPY_OLD_FE, but it hardly seems worth it,
-		 * considering the size of the buffer.
+		 * Load more data if needed.
+		 *
+		 * We look ahead max three bytes in the code below (for the sequence
+		 * \.<CR><NL>).  Make sure we have at least four bytes in the buffer,
+		 * so that the rest of the code in the loop can just assume that the
+		 * data is in the buffer.  Note that we always guarantee that there is
+		 * one \0 in the buffer, after last valid byte; the lookahead code
+		 * below relies on that.
 		 */
-		if (raw_buf_ptr >= copy_buf_len || need_data)
+#define COPY_READ_LINE_LOOKAHEAD	4
+		if (raw_buf_ptr + COPY_READ_LINE_LOOKAHEAD >= copy_buf_len)
 		{
-			REFILL_LINEBUF;
+			if (!hit_eof)
+			{
+				REFILL_LINEBUF;
 
-			/*
-			 * Try to read some more data.  This will certainly reset
-			 * raw_buf_index to zero, and raw_buf_ptr must go with it.
-			 */
-			if (!CopyLoadRawBuf(cstate))
-				hit_eof = true;
-			raw_buf_ptr = 0;
-			copy_buf_len = cstate->raw_buf_len;
+				/*
+				 * Try to read some more data.  This will certainly reset
+				 * raw_buf_index to zero, and raw_buf_ptr must go with it.
+				 */
+				if (!CopyLoadRawBuf(cstate, COPY_READ_LINE_LOOKAHEAD))
+					hit_eof = true;
+				raw_buf_ptr = 0;
+				copy_buf_len = cstate->raw_buf_len;
+			}
 
 			/*
 			 * If we are completely out of data, break out of the loop,
 			 * reporting EOF.
 			 */
-			if (copy_buf_len <= 0)
+			if (copy_buf_len - raw_buf_ptr <= 0)
 			{
 				result = true;
 				break;
 			}
-			need_data = false;
 		}
 
 		/* OK to fetch a character */
@@ -880,20 +842,6 @@ CopyReadLineText(CopyFromState cstate)
 
 		if (cstate->opts.csv_mode)
 		{
-			/*
-			 * If character is '\\' or '\r', we may need to look ahead below.
-			 * Force fetch of the next character if we don't already have it.
-			 * We need to do this before changing CSV state, in case one of
-			 * these characters is also the quote or escape character.
-			 *
-			 * Note: old-protocol does not like forced prefetch, but it's OK
-			 * here since we cannot validly be at EOF.
-			 */
-			if (c == '\\' || c == '\r')
-			{
-				IF_NEED_REFILL_AND_NOT_EOF_CONTINUE(0);
-			}
-
 			/*
 			 * Dealing with quotes and escapes here is mildly tricky. If the
 			 * quote char is also the escape char, there's no problem - we
@@ -927,14 +875,9 @@ CopyReadLineText(CopyFromState cstate)
 				cstate->eol_type == EOL_CRNL)
 			{
 				/*
-				 * If need more data, go back to loop top to load it.
-				 *
-				 * Note that if we are at EOF, c will wind up as '\0' because
-				 * of the guaranteed pad of raw_buf.
+				 * Look at the next character.  If we're at EOF, c2 will wind up as
+				 * '\0' because of the guaranteed pad of raw_buf.
 				 */
-				IF_NEED_REFILL_AND_NOT_EOF_CONTINUE(0);
-
-				/* get next char */
 				c = copy_raw_buf[raw_buf_ptr];
 
 				if (c == '\n')
@@ -1000,7 +943,6 @@ CopyReadLineText(CopyFromState cstate)
 		{
 			char		c2;
 
-			IF_NEED_REFILL_AND_NOT_EOF_CONTINUE(0);
 			IF_NEED_REFILL_AND_EOF_BREAK(0);
 
 			/* -----
@@ -1015,15 +957,8 @@ CopyReadLineText(CopyFromState cstate)
 			{
 				raw_buf_ptr++;	/* consume the '.' */
 
-				/*
-				 * Note: if we loop back for more data here, it does not
-				 * matter that the CSV state change checks are re-executed; we
-				 * will come back here with no important state changed.
-				 */
 				if (cstate->eol_type == EOL_CRNL)
 				{
-					/* Get the next character */
-					IF_NEED_REFILL_AND_NOT_EOF_CONTINUE(0);
 					/* if hit_eof, c2 will become '\0' */
 					c2 = copy_raw_buf[raw_buf_ptr++];
 
@@ -1047,8 +982,6 @@ CopyReadLineText(CopyFromState cstate)
 					}
 				}
 
-				/* Get the next character */
-				IF_NEED_REFILL_AND_NOT_EOF_CONTINUE(0);
 				/* if hit_eof, c2 will become '\0' */
 				c2 = copy_raw_buf[raw_buf_ptr++];
 
@@ -1126,7 +1059,6 @@ not_end_of_copy:
 			mblen_str[0] = c;
 			mblen = pg_encoding_mblen(cstate->file_encoding, mblen_str);
 
-			IF_NEED_REFILL_AND_NOT_EOF_CONTINUE(mblen - 1);
 			IF_NEED_REFILL_AND_EOF_BREAK(mblen - 1);
 			raw_buf_ptr += mblen - 1;
 		}
diff --git a/src/backend/commands/copyto.c b/src/backend/commands/copyto.c
index e04ec1e331b..edbd5d83a0f 100644
--- a/src/backend/commands/copyto.c
+++ b/src/backend/commands/copyto.c
@@ -162,7 +162,7 @@ SendCopyBegin(CopyToState cstate)
 		if (cstate->opts.binary)
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("COPY BINARY is not supported to stdout or from stdin")));
+					 errmsg("COPY BINARY is not supported to stdout or from stdin in protocol version 2")));
 		pq_putemptymessage('H');
 		/* grottiness needed for old COPY OUT protocol */
 		pq_startcopyout();
diff --git a/src/include/commands/copyfrom_internal.h b/src/include/commands/copyfrom_internal.h
index e37942df391..afa70326137 100644
--- a/src/include/commands/copyfrom_internal.h
+++ b/src/include/commands/copyfrom_internal.h
@@ -24,7 +24,7 @@
 typedef enum CopySource
 {
 	COPY_FILE,					/* from file (or a piped program) */
-	COPY_OLD_FE,				/* from frontend (2.0 protocol) */
+	/* protocol version 2 not supported with COPY FROM */
 	COPY_NEW_FE,				/* from frontend (3.0 protocol) */
 	COPY_CALLBACK				/* from callback function */
 } CopySource;
@@ -71,8 +71,7 @@ typedef struct CopyFromStateData
 	CopySource	copy_src;		/* type of copy source */
 	FILE	   *copy_file;		/* used if copy_src == COPY_FILE */
 	StringInfo	fe_msgbuf;		/* used if copy_src == COPY_NEW_FE */
-	bool		reached_eof;	/* true if we read to end of copy data (not
-								 * all copy_src types maintain this) */
+	bool		reached_eof;	/* true if we read to end of copy data */
 
 	EolType		eol_type;		/* EOL type of input */
 	int			file_encoding;	/* file or remote side's character encoding */
-- 
2.30.0

#2Tom Lane
tgl@sss.pgh.pa.us
In reply to: Heikki Linnakangas (#1)
Re: Removing support for COPY FROM STDIN in protocol version 2

Heikki Linnakangas <hlinnaka@iki.fi> writes:

I propose that we remove server support for COPY FROM STDIN with
protocol version 2, per attached patch. Even if we could still support
it, it would be a very rarely used and tested codepath, prone to bugs.
Perhaps we could remove support for the old protocol altogether, but I'm
not proposing that we go that far just yet.

I'm not really on board with half-baked removal of protocol 2.
If we're going to kill it we should just kill it altogether.
(The argument that it's untested surely applies to the rest
of the P2 code as well.)

I have a vague recollection that JDBC users still like to use
protocol 2 for some reason --- is that out of date?

regards, tom lane

#3Alvaro Herrera
alvherre@alvh.no-ip.org
In reply to: Tom Lane (#2)
Re: Removing support for COPY FROM STDIN in protocol version 2

On 2021-Feb-03, Tom Lane wrote:

I have a vague recollection that JDBC users still like to use
protocol 2 for some reason --- is that out of date?

2016:

commit c3d8571e53cc5b702dae2f832b02c872ad44c3b7
Author: Vladimir Sitnikov <sitnikov.vladimir@gmail.com>
AuthorDate: Sat Aug 6 12:22:17 2016 +0300
CommitDate: Sat Aug 13 11:27:16 2016 +0300

fix: support cases when user-provided queries have 'returning'

This change includes: drop v2 protocol support, and query parsing refactoring.
Currently query parse cache is still per-connection, however "returningColumNames"
are part of cache key, thus the parse cache can be made global.

This fixes #488 (see org.postgresql.test.jdbc3.GeneratedKeysTest)

This commit does remove all files in
pgjdbc/src/main/java/org/postgresql/core/v2/, leaving only "v3/".

--
�lvaro Herrera Valdivia, Chile
"Those who use electric razors are infidels destined to burn in hell while
we drink from rivers of beer, download free vids and mingle with naked
well shaved babes." (http://slashdot.org/comments.pl?sid=44793&amp;cid=4647152)

#4Tom Lane
tgl@sss.pgh.pa.us
In reply to: Alvaro Herrera (#3)
Re: Removing support for COPY FROM STDIN in protocol version 2

Alvaro Herrera <alvherre@alvh.no-ip.org> writes:

On 2021-Feb-03, Tom Lane wrote:

I have a vague recollection that JDBC users still like to use
protocol 2 for some reason --- is that out of date?

[ yes, since 2016 ]

Then let's kill it dead, server and libpq both.

regards, tom lane

#5Heikki Linnakangas
hlinnaka@iki.fi
In reply to: Tom Lane (#4)
Re: Removing support for COPY FROM STDIN in protocol version 2

On 03/02/2021 18:29, Tom Lane wrote:

Alvaro Herrera <alvherre@alvh.no-ip.org> writes:

On 2021-Feb-03, Tom Lane wrote:

I have a vague recollection that JDBC users still like to use
protocol 2 for some reason --- is that out of date?

[ yes, since 2016 ]

Then let's kill it dead, server and libpq both.

Ok, works for me. I'll prepare a larger patch to do that.

Since we're on a removal-spree, it'd also be nice to get rid of the
"fast-path" function call interface, PQfn(). However, libpq is using it
internally in the lo_*() functions, so if we remove it from the server,
lo_*() will stop working with old libpq versions. It would be good to
change those functions now to use PQexecParams() instead, so that we
could remove the fast-path server support in the future.

- Heikki

#6Tom Lane
tgl@sss.pgh.pa.us
In reply to: Heikki Linnakangas (#5)
Re: Removing support for COPY FROM STDIN in protocol version 2

Heikki Linnakangas <hlinnaka@iki.fi> writes:

Since we're on a removal-spree, it'd also be nice to get rid of the
"fast-path" function call interface, PQfn(). However, libpq is using it
internally in the lo_*() functions, so if we remove it from the server,
lo_*() will stop working with old libpq versions. It would be good to
change those functions now to use PQexecParams() instead, so that we
could remove the fast-path server support in the future.

I'm disinclined to touch that. It is considered part of protocol v3,
and there is no very good reason to suppose that nothing but libpq
is using it. Besides, what would it really save? fastpath.c has
not been a source of maintenance problems.

regards, tom lane

#7Michael Paquier
michael@paquier.xyz
In reply to: Tom Lane (#4)
Re: Removing support for COPY FROM STDIN in protocol version 2

On Wed, Feb 03, 2021 at 11:29:37AM -0500, Tom Lane wrote:

Then let's kill it dead, server and libpq both.

Yeah.
--
Michael

#8Heikki Linnakangas
hlinnaka@iki.fi
In reply to: Michael Paquier (#7)
2 attachment(s)
Re: Removing support for COPY FROM STDIN in protocol version 2

On 04/02/2021 08:54, Michael Paquier wrote:

On Wed, Feb 03, 2021 at 11:29:37AM -0500, Tom Lane wrote:

Then let's kill it dead, server and libpq both.

Yeah.

Ok, here we go.

One interesting thing I noticed while doing this:

Up until now, we always used the old protocol for errors that happened
early in backend startup, before we processed the client's protocol
version and set the FrontendProtocol variable. I'm sure that made sense
when V3 was introduced, but it was a surprise to me, and I didn't find
that documented anywhere. I changed it so that we use V3 errors, if
FrontendProtocol is not yet set.

However, I kept rudimentary support for sending errors in protocol
version 2. This way, if a client tries to connect with an old client, we
still send the "unsupported frontend protocol" error in the old format.
Likewise, I kept the code in libpq to understand v2 ErrorResponse
messages during authentication.

- Heikki

Attachments:

0001-Remove-server-and-libpq-support-for-old-FE-BE-protoc.patchtext/x-patch; charset=UTF-8; name=0001-Remove-server-and-libpq-support-for-old-FE-BE-protoc.patchDownload
From b497f65714d5c6a96b4a859ffbf2637cb3c9fb82 Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <heikki.linnakangas@iki.fi>
Date: Thu, 4 Feb 2021 12:31:34 +0200
Subject: [PATCH 1/2] Remove server and libpq support for old FE/BE protocol
 version 2.

Protocol version 3 was introduced in PostgreSQL 7.4. There shouldn't be
many clients or servers left out there without version 3 support. But as
a courtesy, I kept just enough of the old protocol support that we can
still send the "unsupported protocol version" error in v2 format, so that
old clients can display the message properly. Likewise, libpq still
understands v2 ErrorResponse messages when establishing connection.

The impetus to do this now is that I'm working on a patch to change the
way the encoding conversion performed in COPY FROM, so that we convert
the data in larger chunks, before scanning the input for line boundaries.
We cannot do that easily with the old protocol, because it requires
parsing the input one byte at a time to detect the end-of-copy marker.

Discussion: https://www.postgresql.org/message-id/9ec25819-0a8a-d51a-17dc-4150bb3cca3b%40iki.fi
---
 doc/src/sgml/libpq.sgml                  |   79 +-
 src/backend/access/common/printtup.c     |  248 +---
 src/backend/commands/async.c             |    3 +-
 src/backend/commands/copyfrom.c          |    7 -
 src/backend/commands/copyfromparse.c     |   83 +-
 src/backend/commands/copyto.c            |  127 +-
 src/backend/libpq/auth.c                 |   80 +-
 src/backend/libpq/pqcomm.c               |  215 +--
 src/backend/libpq/pqmq.c                 |   18 +-
 src/backend/postmaster/postmaster.c      |   42 +-
 src/backend/tcop/dest.c                  |   22 +-
 src/backend/tcop/fastpath.c              |  165 +--
 src/backend/tcop/postgres.c              |   71 +-
 src/backend/utils/error/elog.c           |   64 +-
 src/backend/utils/misc/guc.c             |    6 +-
 src/bin/psql/common.c                    |   10 -
 src/bin/psql/copy.c                      |    4 +-
 src/include/commands/copyfrom_internal.h |    3 +-
 src/include/libpq/libpq.h                |    7 +-
 src/include/libpq/pqcomm.h               |   33 +-
 src/include/tcop/fastpath.h              |    1 -
 src/include/utils/elog.h                 |    2 -
 src/interfaces/libpq/Makefile            |    1 -
 src/interfaces/libpq/fe-auth.c           |    8 +-
 src/interfaces/libpq/fe-connect.c        |  148 +-
 src/interfaces/libpq/fe-exec.c           |  231 +---
 src/interfaces/libpq/fe-misc.c           |   21 +-
 src/interfaces/libpq/fe-protocol2.c      | 1610 ----------------------
 src/interfaces/libpq/fe-protocol3.c      |    6 +-
 src/interfaces/libpq/libpq-fe.h          |    1 -
 src/interfaces/libpq/libpq-int.h         |   35 +-
 31 files changed, 280 insertions(+), 3071 deletions(-)
 delete mode 100644 src/interfaces/libpq/fe-protocol2.c

diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index b7a82453f0d..262850bc2cf 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -2174,20 +2174,6 @@ const char *PQparameterStatus(const PGconn *conn, const char *paramName);
        cannot change after startup.
       </para>
 
-      <para>
-       Pre-3.0-protocol servers do not report parameter settings, but
-       <application>libpq</application> includes logic to obtain values for
-       <varname>server_version</varname> and <varname>client_encoding</varname> anyway.
-       Applications are encouraged to use <xref linkend="libpq-PQparameterStatus"/>
-       rather than <foreignphrase>ad hoc</foreignphrase> code to determine these values.
-       (Beware however that on a pre-3.0 connection, changing
-       <varname>client_encoding</varname> via <command>SET</command> after connection
-       startup will not be reflected by <xref linkend="libpq-PQparameterStatus"/>.)
-       For <varname>server_version</varname>, see also
-       <xref linkend="libpq-PQserverVersion"/>, which returns the information in a
-       numeric form that is much easier to compare against.
-      </para>
-
       <para>
        If no value for <varname>standard_conforming_strings</varname> is reported,
        applications can assume it is <literal>off</literal>, that is, backslashes
@@ -2214,15 +2200,12 @@ const char *PQparameterStatus(const PGconn *conn, const char *paramName);
 int PQprotocolVersion(const PGconn *conn);
 </synopsis>
        Applications might wish to use this function to determine whether certain
-       features are supported.  Currently, the possible values are 2 (2.0
-       protocol), 3 (3.0 protocol), or zero (connection bad).  The
-       protocol version will
+       features are supported.  Currently, the possible values are 3
+       (3.0 protocol), or zero (connection bad).  The protocol version will
        not change after connection startup is complete, but it could
-       theoretically change during a connection reset.  The 3.0 protocol
-       will normally be used when communicating with
-       <productname>PostgreSQL</productname> 7.4 or later servers; pre-7.4 servers
-       support only protocol 2.0.  (Protocol 1.0 is obsolete and not
-       supported by <application>libpq</application>.)
+       theoretically change during a connection reset.  The 3.0 protocol is
+       supported by <productname>PostgreSQL</productname> server versions 7.4
+       and above.
       </para>
      </listitem>
     </varlistentry>
@@ -2639,8 +2622,7 @@ PGresult *PQexecParams(PGconn *conn,
         <xref linkend="libpq-PQexecParams"/> is like <xref linkend="libpq-PQexec"/>, but offers additional
         functionality: parameter values can be specified separately from the command
         string proper, and query results can be requested in either text or binary
-        format.  <xref linkend="libpq-PQexecParams"/> is supported only in protocol 3.0 and later
-        connections; it will fail when using protocol 2.0.
+        format.
        </para>
 
        <para>
@@ -2817,8 +2799,6 @@ PGresult *PQprepare(PGconn *conn,
         execution with <xref linkend="libpq-PQexecPrepared"/>.  This feature allows
         commands to be executed repeatedly without being parsed and
         planned each time;  see <xref linkend="sql-prepare"/> for details.
-        <xref linkend="libpq-PQprepare"/> is supported only in protocol 3.0 and later
-        connections; it will fail when using protocol 2.0.
        </para>
 
        <para>
@@ -2892,9 +2872,7 @@ PGresult *PQexecPrepared(PGconn *conn,
         This feature allows commands that will be used repeatedly to be
         parsed and planned just once, rather than each time they are
         executed.  The statement must have been prepared previously in
-        the current session.  <xref linkend="libpq-PQexecPrepared"/> is supported
-        only in protocol 3.0 and later connections; it will fail when
-        using protocol 2.0.
+        the current session.
        </para>
 
        <para>
@@ -2921,8 +2899,6 @@ PGresult *PQdescribePrepared(PGconn *conn, const char *stmtName);
        <para>
         <xref linkend="libpq-PQdescribePrepared"/> allows an application to obtain
         information about a previously prepared statement.
-        <xref linkend="libpq-PQdescribePrepared"/> is supported only in protocol 3.0
-        and later connections; it will fail when using protocol 2.0.
        </para>
 
        <para>
@@ -2959,8 +2935,6 @@ PGresult *PQdescribePortal(PGconn *conn, const char *portalName);
         (<application>libpq</application> does not provide any direct access to
         portals, but you can use this function to inspect the properties
         of a cursor created with a <command>DECLARE CURSOR</command> SQL command.)
-        <xref linkend="libpq-PQdescribePortal"/> is supported only in protocol 3.0
-        and later connections; it will fail when using protocol 2.0.
        </para>
 
        <para>
@@ -3466,8 +3440,6 @@ char *PQresultErrorField(const PGresult *res, int fieldcode);
        <para>
         Errors generated internally by <application>libpq</application> will
         have severity and primary message, but typically no other fields.
-        Errors returned by a pre-3.0-protocol server will include severity and
-        primary message, and sometimes a detail message, but no other fields.
        </para>
 
        <para>
@@ -3628,8 +3600,7 @@ Oid PQftable(const PGresult *res,
 
       <para>
        <literal>InvalidOid</literal> is returned if the column number is out of range,
-       or if the specified column is not a simple reference to a table column,
-       or when using pre-3.0 protocol.
+       or if the specified column is not a simple reference to a table column.
        You can query the system table <literal>pg_class</literal> to determine
        exactly which table is referenced.
       </para>
@@ -3659,8 +3630,7 @@ int PQftablecol(const PGresult *res,
 
       <para>
        Zero is returned if the column number is out of range, or if the
-       specified column is not a simple reference to a table column, or
-       when using pre-3.0 protocol.
+       specified column is not a simple reference to a table column.
       </para>
      </listitem>
     </varlistentry>
@@ -4493,8 +4463,8 @@ int PQsendQueryParams(PGconn *conn,
        query parameters can be specified separately from the query string.
        The function's parameters are handled identically to
        <xref linkend="libpq-PQexecParams"/>.  Like
-       <xref linkend="libpq-PQexecParams"/>, it will not work on 2.0-protocol
-       connections, and it allows only one command in the query string.
+       <xref linkend="libpq-PQexecParams"/>, it allows only one command in the
+       query string.
       </para>
      </listitem>
     </varlistentry>
@@ -4519,9 +4489,7 @@ int PQsendPrepare(PGconn *conn,
        After a successful call, call <xref linkend="libpq-PQgetResult"/> to
        determine whether the server successfully created the prepared
        statement.  The function's parameters are handled identically to
-       <xref linkend="libpq-PQprepare"/>.  Like
-       <xref linkend="libpq-PQprepare"/>, it will not work on 2.0-protocol
-       connections.
+       <xref linkend="libpq-PQprepare"/>.
       </para>
      </listitem>
     </varlistentry>
@@ -4547,9 +4515,7 @@ int PQsendQueryPrepared(PGconn *conn,
        the command to be executed is specified by naming a
        previously-prepared statement, instead of giving a query string.
        The function's parameters are handled identically to
-       <xref linkend="libpq-PQexecPrepared"/>.  Like
-       <xref linkend="libpq-PQexecPrepared"/>, it will not work on
-       2.0-protocol connections.
+       <xref linkend="libpq-PQexecPrepared"/>.
       </para>
      </listitem>
     </varlistentry>
@@ -4569,9 +4535,7 @@ int PQsendDescribePrepared(PGconn *conn, const char *stmtName);
        it returns 1 if it was able to dispatch the request, and 0 if not.
        After a successful call, call <xref linkend="libpq-PQgetResult"/> to
        obtain the results.  The function's parameters are handled
-       identically to <xref linkend="libpq-PQdescribePrepared"/>.  Like
-       <xref linkend="libpq-PQdescribePrepared"/>, it will not work on
-       2.0-protocol connections.
+       identically to <xref linkend="libpq-PQdescribePrepared"/>.
       </para>
      </listitem>
     </varlistentry>
@@ -4591,9 +4555,7 @@ int PQsendDescribePortal(PGconn *conn, const char *portalName);
        it returns 1 if it was able to dispatch the request, and 0 if not.
        After a successful call, call <xref linkend="libpq-PQgetResult"/> to
        obtain the results.  The function's parameters are handled
-       identically to <xref linkend="libpq-PQdescribePortal"/>.  Like
-       <xref linkend="libpq-PQdescribePortal"/>, it will not work on
-       2.0-protocol connections.
+       identically to <xref linkend="libpq-PQdescribePortal"/>.
       </para>
      </listitem>
     </varlistentry>
@@ -5360,13 +5322,6 @@ typedef struct pgNotify
    </variablelist>
   </para>
 
-  <note>
-   <para>
-    These additional data values are only available when using protocol
-    3.0.  When using protocol 2.0, all these functions will return 0.
-   </para>
-  </note>
-
   <sect2 id="libpq-copy-send">
    <title>Functions for Sending <command>COPY</command> Data</title>
 
@@ -5431,9 +5386,7 @@ int PQputCopyEnd(PGconn *conn,
        <parameter>errormsg</parameter> used as the error message.  (One should not
        assume that this exact error message will come back from the server,
        however, as the server might have already failed the
-       <command>COPY</command> for its own reasons.  Also note that the option
-       to force failure does not work when using pre-3.0-protocol
-       connections.)
+       <command>COPY</command> for its own reasons.)
       </para>
 
       <para>
diff --git a/src/backend/access/common/printtup.c b/src/backend/access/common/printtup.c
index 4468480e9b3..54b539f6fb6 100644
--- a/src/backend/access/common/printtup.c
+++ b/src/backend/access/common/printtup.c
@@ -27,16 +27,9 @@
 static void printtup_startup(DestReceiver *self, int operation,
 							 TupleDesc typeinfo);
 static bool printtup(TupleTableSlot *slot, DestReceiver *self);
-static bool printtup_20(TupleTableSlot *slot, DestReceiver *self);
-static bool printtup_internal_20(TupleTableSlot *slot, DestReceiver *self);
 static void printtup_shutdown(DestReceiver *self);
 static void printtup_destroy(DestReceiver *self);
 
-static void SendRowDescriptionCols_2(StringInfo buf, TupleDesc typeinfo,
-									 List *targetlist, int16 *formats);
-static void SendRowDescriptionCols_3(StringInfo buf, TupleDesc typeinfo,
-									 List *targetlist, int16 *formats);
-
 /* ----------------------------------------------------------------
  *		printtup / debugtup support
  * ----------------------------------------------------------------
@@ -112,19 +105,6 @@ SetRemoteDestReceiverParams(DestReceiver *self, Portal portal)
 		   myState->pub.mydest == DestRemoteExecute);
 
 	myState->portal = portal;
-
-	if (PG_PROTOCOL_MAJOR(FrontendProtocol) < 3)
-	{
-		/*
-		 * In protocol 2.0 the Bind message does not exist, so there is no way
-		 * for the columns to have different print formats; it's sufficient to
-		 * look at the first one.
-		 */
-		if (portal->formats && portal->formats[0] != 0)
-			myState->pub.receiveSlot = printtup_internal_20;
-		else
-			myState->pub.receiveSlot = printtup_20;
-	}
 }
 
 static void
@@ -149,21 +129,6 @@ printtup_startup(DestReceiver *self, int operation, TupleDesc typeinfo)
 												"printtup",
 												ALLOCSET_DEFAULT_SIZES);
 
-	if (PG_PROTOCOL_MAJOR(FrontendProtocol) < 3)
-	{
-		/*
-		 * Send portal name to frontend (obsolete cruft, gone in proto 3.0)
-		 *
-		 * If portal name not specified, use "blank" portal.
-		 */
-		const char *portalName = portal->name;
-
-		if (portalName == NULL || portalName[0] == '\0')
-			portalName = "blank";
-
-		pq_puttextmessage('P', portalName);
-	}
-
 	/*
 	 * If we are supposed to emit row descriptions, then send the tuple
 	 * descriptor of the tuples.
@@ -202,31 +167,14 @@ SendRowDescriptionMessage(StringInfo buf, TupleDesc typeinfo,
 						  List *targetlist, int16 *formats)
 {
 	int			natts = typeinfo->natts;
-	int			proto = PG_PROTOCOL_MAJOR(FrontendProtocol);
+	int			i;
+	ListCell   *tlist_item = list_head(targetlist);
 
 	/* tuple descriptor message type */
 	pq_beginmessage_reuse(buf, 'T');
 	/* # of attrs in tuples */
 	pq_sendint16(buf, natts);
 
-	if (proto >= 3)
-		SendRowDescriptionCols_3(buf, typeinfo, targetlist, formats);
-	else
-		SendRowDescriptionCols_2(buf, typeinfo, targetlist, formats);
-
-	pq_endmessage_reuse(buf);
-}
-
-/*
- * Send description for each column when using v3+ protocol
- */
-static void
-SendRowDescriptionCols_3(StringInfo buf, TupleDesc typeinfo, List *targetlist, int16 *formats)
-{
-	int			natts = typeinfo->natts;
-	int			i;
-	ListCell   *tlist_item = list_head(targetlist);
-
 	/*
 	 * Preallocate memory for the entire message to be sent. That allows to
 	 * use the significantly faster inline pqformat.h functions and to avoid
@@ -291,33 +239,8 @@ SendRowDescriptionCols_3(StringInfo buf, TupleDesc typeinfo, List *targetlist, i
 		pq_writeint32(buf, atttypmod);
 		pq_writeint16(buf, format);
 	}
-}
-
-/*
- * Send description for each column when using v2 protocol
- */
-static void
-SendRowDescriptionCols_2(StringInfo buf, TupleDesc typeinfo, List *targetlist, int16 *formats)
-{
-	int			natts = typeinfo->natts;
-	int			i;
-
-	for (i = 0; i < natts; ++i)
-	{
-		Form_pg_attribute att = TupleDescAttr(typeinfo, i);
-		Oid			atttypid = att->atttypid;
-		int32		atttypmod = att->atttypmod;
 
-		/* If column is a domain, send the base type and typmod instead */
-		atttypid = getBaseTypeAndTypmod(atttypid, &atttypmod);
-
-		pq_sendstring(buf, NameStr(att->attname));
-		/* column ID only info appears in protocol 3.0 and up */
-		pq_sendint32(buf, atttypid);
-		pq_sendint16(buf, att->attlen);
-		pq_sendint32(buf, atttypmod);
-		/* format info only appears in protocol 3.0 and up */
-	}
+	pq_endmessage_reuse(buf);
 }
 
 /*
@@ -371,7 +294,7 @@ printtup_prepare_info(DR_printtup *myState, TupleDesc typeinfo, int numAttrs)
 }
 
 /* ----------------
- *		printtup --- print a tuple in protocol 3.0
+ *		printtup --- send a tuple to the client
  * ----------------
  */
 static bool
@@ -455,84 +378,6 @@ printtup(TupleTableSlot *slot, DestReceiver *self)
 	return true;
 }
 
-/* ----------------
- *		printtup_20 --- print a tuple in protocol 2.0
- * ----------------
- */
-static bool
-printtup_20(TupleTableSlot *slot, DestReceiver *self)
-{
-	TupleDesc	typeinfo = slot->tts_tupleDescriptor;
-	DR_printtup *myState = (DR_printtup *) self;
-	MemoryContext oldcontext;
-	StringInfo	buf = &myState->buf;
-	int			natts = typeinfo->natts;
-	int			i,
-				j,
-				k;
-
-	/* Set or update my derived attribute info, if needed */
-	if (myState->attrinfo != typeinfo || myState->nattrs != natts)
-		printtup_prepare_info(myState, typeinfo, natts);
-
-	/* Make sure the tuple is fully deconstructed */
-	slot_getallattrs(slot);
-
-	/* Switch into per-row context so we can recover memory below */
-	oldcontext = MemoryContextSwitchTo(myState->tmpcontext);
-
-	/*
-	 * tell the frontend to expect new tuple data (in ASCII style)
-	 */
-	pq_beginmessage_reuse(buf, 'D');
-
-	/*
-	 * send a bitmap of which attributes are not null
-	 */
-	j = 0;
-	k = 1 << 7;
-	for (i = 0; i < natts; ++i)
-	{
-		if (!slot->tts_isnull[i])
-			j |= k;				/* set bit if not null */
-		k >>= 1;
-		if (k == 0)				/* end of byte? */
-		{
-			pq_sendint8(buf, j);
-			j = 0;
-			k = 1 << 7;
-		}
-	}
-	if (k != (1 << 7))			/* flush last partial byte */
-		pq_sendint8(buf, j);
-
-	/*
-	 * send the attributes of this tuple
-	 */
-	for (i = 0; i < natts; ++i)
-	{
-		PrinttupAttrInfo *thisState = myState->myinfo + i;
-		Datum		attr = slot->tts_values[i];
-		char	   *outputstr;
-
-		if (slot->tts_isnull[i])
-			continue;
-
-		Assert(thisState->format == 0);
-
-		outputstr = OutputFunctionCall(&thisState->finfo, attr);
-		pq_sendcountedtext(buf, outputstr, strlen(outputstr), true);
-	}
-
-	pq_endmessage_reuse(buf);
-
-	/* Return to caller's context, and flush row's temporary memory */
-	MemoryContextSwitchTo(oldcontext);
-	MemoryContextReset(myState->tmpcontext);
-
-	return true;
-}
-
 /* ----------------
  *		printtup_shutdown
  * ----------------
@@ -638,88 +483,3 @@ debugtup(TupleTableSlot *slot, DestReceiver *self)
 
 	return true;
 }
-
-/* ----------------
- *		printtup_internal_20 --- print a binary tuple in protocol 2.0
- *
- * We use a different message type, i.e. 'B' instead of 'D' to
- * indicate a tuple in internal (binary) form.
- *
- * This is largely same as printtup_20, except we use binary formatting.
- * ----------------
- */
-static bool
-printtup_internal_20(TupleTableSlot *slot, DestReceiver *self)
-{
-	TupleDesc	typeinfo = slot->tts_tupleDescriptor;
-	DR_printtup *myState = (DR_printtup *) self;
-	MemoryContext oldcontext;
-	StringInfo	buf = &myState->buf;
-	int			natts = typeinfo->natts;
-	int			i,
-				j,
-				k;
-
-	/* Set or update my derived attribute info, if needed */
-	if (myState->attrinfo != typeinfo || myState->nattrs != natts)
-		printtup_prepare_info(myState, typeinfo, natts);
-
-	/* Make sure the tuple is fully deconstructed */
-	slot_getallattrs(slot);
-
-	/* Switch into per-row context so we can recover memory below */
-	oldcontext = MemoryContextSwitchTo(myState->tmpcontext);
-
-	/*
-	 * tell the frontend to expect new tuple data (in binary style)
-	 */
-	pq_beginmessage_reuse(buf, 'B');
-
-	/*
-	 * send a bitmap of which attributes are not null
-	 */
-	j = 0;
-	k = 1 << 7;
-	for (i = 0; i < natts; ++i)
-	{
-		if (!slot->tts_isnull[i])
-			j |= k;				/* set bit if not null */
-		k >>= 1;
-		if (k == 0)				/* end of byte? */
-		{
-			pq_sendint8(buf, j);
-			j = 0;
-			k = 1 << 7;
-		}
-	}
-	if (k != (1 << 7))			/* flush last partial byte */
-		pq_sendint8(buf, j);
-
-	/*
-	 * send the attributes of this tuple
-	 */
-	for (i = 0; i < natts; ++i)
-	{
-		PrinttupAttrInfo *thisState = myState->myinfo + i;
-		Datum		attr = slot->tts_values[i];
-		bytea	   *outputbytes;
-
-		if (slot->tts_isnull[i])
-			continue;
-
-		Assert(thisState->format == 1);
-
-		outputbytes = SendFunctionCall(&thisState->finfo, attr);
-		pq_sendint32(buf, VARSIZE(outputbytes) - VARHDRSZ);
-		pq_sendbytes(buf, VARDATA(outputbytes),
-					 VARSIZE(outputbytes) - VARHDRSZ);
-	}
-
-	pq_endmessage_reuse(buf);
-
-	/* Return to caller's context, and flush row's temporary memory */
-	MemoryContextSwitchTo(oldcontext);
-	MemoryContextReset(myState->tmpcontext);
-
-	return true;
-}
diff --git a/src/backend/commands/async.c b/src/backend/commands/async.c
index 42b232d98b1..4b16fb56825 100644
--- a/src/backend/commands/async.c
+++ b/src/backend/commands/async.c
@@ -2311,8 +2311,7 @@ NotifyMyFrontEnd(const char *channel, const char *payload, int32 srcPid)
 		pq_beginmessage(&buf, 'A');
 		pq_sendint32(&buf, srcPid);
 		pq_sendstring(&buf, channel);
-		if (PG_PROTOCOL_MAJOR(FrontendProtocol) >= 3)
-			pq_sendstring(&buf, payload);
+		pq_sendstring(&buf, payload);
 		pq_endmessage(&buf);
 
 		/*
diff --git a/src/backend/commands/copyfrom.c b/src/backend/commands/copyfrom.c
index c39cc736ed2..6d43d056cca 100644
--- a/src/backend/commands/copyfrom.c
+++ b/src/backend/commands/copyfrom.c
@@ -1125,13 +1125,6 @@ CopyFrom(CopyFromState cstate)
 
 	MemoryContextSwitchTo(oldcontext);
 
-	/*
-	 * In the old protocol, tell pqcomm that we can process normal protocol
-	 * messages again.
-	 */
-	if (cstate->copy_src == COPY_OLD_FE)
-		pq_endmsgread();
-
 	/* Execute AFTER STATEMENT insertion triggers */
 	ExecASInsertTriggers(estate, target_resultRelInfo, cstate->transition_capture);
 
diff --git a/src/backend/commands/copyfromparse.c b/src/backend/commands/copyfromparse.c
index 4c74067f849..dd629668540 100644
--- a/src/backend/commands/copyfromparse.c
+++ b/src/backend/commands/copyfromparse.c
@@ -124,35 +124,19 @@ static int	CopyReadBinaryData(CopyFromState cstate, char *dest, int nbytes);
 void
 ReceiveCopyBegin(CopyFromState cstate)
 {
-	if (PG_PROTOCOL_MAJOR(FrontendProtocol) >= 3)
-	{
-		/* new way */
-		StringInfoData buf;
-		int			natts = list_length(cstate->attnumlist);
-		int16		format = (cstate->opts.binary ? 1 : 0);
-		int			i;
-
-		pq_beginmessage(&buf, 'G');
-		pq_sendbyte(&buf, format);	/* overall format */
-		pq_sendint16(&buf, natts);
-		for (i = 0; i < natts; i++)
-			pq_sendint16(&buf, format); /* per-column formats */
-		pq_endmessage(&buf);
-		cstate->copy_src = COPY_NEW_FE;
-		cstate->fe_msgbuf = makeStringInfo();
-	}
-	else
-	{
-		/* old way */
-		if (cstate->opts.binary)
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("COPY BINARY is not supported to stdout or from stdin")));
-		pq_putemptymessage('G');
-		/* any error in old protocol will make us lose sync */
-		pq_startmsgread();
-		cstate->copy_src = COPY_OLD_FE;
-	}
+	StringInfoData buf;
+	int			natts = list_length(cstate->attnumlist);
+	int16		format = (cstate->opts.binary ? 1 : 0);
+	int			i;
+
+	pq_beginmessage(&buf, 'G');
+	pq_sendbyte(&buf, format);	/* overall format */
+	pq_sendint16(&buf, natts);
+	for (i = 0; i < natts; i++)
+		pq_sendint16(&buf, format); /* per-column formats */
+	pq_endmessage(&buf);
+	cstate->copy_src = COPY_FRONTEND;
+	cstate->fe_msgbuf = makeStringInfo();
 	/* We *must* flush here to ensure FE knows it can send. */
 	pq_flush();
 }
@@ -228,25 +212,7 @@ CopyGetData(CopyFromState cstate, void *databuf, int minread, int maxread)
 			if (bytesread == 0)
 				cstate->reached_eof = true;
 			break;
-		case COPY_OLD_FE:
-
-			/*
-			 * We cannot read more than minread bytes (which in practice is 1)
-			 * because old protocol doesn't have any clear way of separating
-			 * the COPY stream from following data.  This is slow, but not any
-			 * slower than the code path was originally, and we don't care
-			 * much anymore about the performance of old protocol.
-			 */
-			if (pq_getbytes((char *) databuf, minread))
-			{
-				/* Only a \. terminator is legal EOF in old protocol */
-				ereport(ERROR,
-						(errcode(ERRCODE_CONNECTION_FAILURE),
-						 errmsg("unexpected EOF on client connection with an open transaction")));
-			}
-			bytesread = minread;
-			break;
-		case COPY_NEW_FE:
+		case COPY_FRONTEND:
 			while (maxread > 0 && bytesread < minread && !cstate->reached_eof)
 			{
 				int			avail;
@@ -619,21 +585,16 @@ NextCopyFrom(CopyFromState cstate, ExprContext *econtext,
 		if (fld_count == -1)
 		{
 			/*
-			 * Received EOF marker.  In a V3-protocol copy, wait for the
-			 * protocol-level EOF, and complain if it doesn't come
-			 * immediately.  This ensures that we correctly handle CopyFail,
-			 * if client chooses to send that now.
-			 *
-			 * Note that we MUST NOT try to read more data in an old-protocol
-			 * copy, since there is no protocol-level EOF marker then.  We
-			 * could go either way for copy from file, but choose to throw
-			 * error if there's data after the EOF marker, for consistency
-			 * with the new-protocol case.
+			 * Received EOF marker.  Wait for the protocol-level EOF, and
+			 * complain if it doesn't come immediately.  In COPY FROM STDIN,
+			 * this ensures that we correctly handle CopyFail, if client
+			 * chooses to send that now.  When copying from file, we could
+			 * ignore the rest of the file like in text mode, but we choose to
+			 * be consistent with the COPY FROM STDIN case.
 			 */
 			char		dummy;
 
-			if (cstate->copy_src != COPY_OLD_FE &&
-				CopyReadBinaryData(cstate, &dummy, 1) > 0)
+			if (CopyReadBinaryData(cstate, &dummy, 1) > 0)
 				ereport(ERROR,
 						(errcode(ERRCODE_BAD_COPY_FILE_FORMAT),
 						 errmsg("received copy data after EOF marker")));
@@ -712,7 +673,7 @@ CopyReadLine(CopyFromState cstate)
 		 * after \. up to the protocol end of copy data.  (XXX maybe better
 		 * not to treat \. as special?)
 		 */
-		if (cstate->copy_src == COPY_NEW_FE)
+		if (cstate->copy_src == COPY_FRONTEND)
 		{
 			do
 			{
diff --git a/src/backend/commands/copyto.c b/src/backend/commands/copyto.c
index e04ec1e331b..46155015cfd 100644
--- a/src/backend/commands/copyto.c
+++ b/src/backend/commands/copyto.c
@@ -50,8 +50,7 @@
 typedef enum CopyDest
 {
 	COPY_FILE,					/* to file (or a piped program) */
-	COPY_OLD_FE,				/* to frontend (2.0 protocol) */
-	COPY_NEW_FE,				/* to frontend (3.0 protocol) */
+	COPY_FRONTEND,				/* to frontend */
 } CopyDest;
 
 /*
@@ -116,7 +115,6 @@ static const char BinarySignature[11] = "PGCOPY\n\377\r\n\0";
 /* non-export function prototypes */
 static void EndCopy(CopyToState cstate);
 static void ClosePipeToProgram(CopyToState cstate);
-static uint64 CopyTo(CopyToState cstate);
 static void CopyOneRowTo(CopyToState cstate, TupleTableSlot *slot);
 static void CopyAttributeOutText(CopyToState cstate, char *string);
 static void CopyAttributeOutCSV(CopyToState cstate, char *string,
@@ -140,53 +138,27 @@ static void CopySendInt16(CopyToState cstate, int16 val);
 static void
 SendCopyBegin(CopyToState cstate)
 {
-	if (PG_PROTOCOL_MAJOR(FrontendProtocol) >= 3)
-	{
-		/* new way */
-		StringInfoData buf;
-		int			natts = list_length(cstate->attnumlist);
-		int16		format = (cstate->opts.binary ? 1 : 0);
-		int			i;
-
-		pq_beginmessage(&buf, 'H');
-		pq_sendbyte(&buf, format);	/* overall format */
-		pq_sendint16(&buf, natts);
-		for (i = 0; i < natts; i++)
-			pq_sendint16(&buf, format); /* per-column formats */
-		pq_endmessage(&buf);
-		cstate->copy_dest = COPY_NEW_FE;
-	}
-	else
-	{
-		/* old way */
-		if (cstate->opts.binary)
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("COPY BINARY is not supported to stdout or from stdin")));
-		pq_putemptymessage('H');
-		/* grottiness needed for old COPY OUT protocol */
-		pq_startcopyout();
-		cstate->copy_dest = COPY_OLD_FE;
-	}
+	StringInfoData buf;
+	int			natts = list_length(cstate->attnumlist);
+	int16		format = (cstate->opts.binary ? 1 : 0);
+	int			i;
+
+	pq_beginmessage(&buf, 'H');
+	pq_sendbyte(&buf, format);	/* overall format */
+	pq_sendint16(&buf, natts);
+	for (i = 0; i < natts; i++)
+		pq_sendint16(&buf, format); /* per-column formats */
+	pq_endmessage(&buf);
+	cstate->copy_dest = COPY_FRONTEND;
 }
 
 static void
 SendCopyEnd(CopyToState cstate)
 {
-	if (cstate->copy_dest == COPY_NEW_FE)
-	{
-		/* Shouldn't have any unsent data */
-		Assert(cstate->fe_msgbuf->len == 0);
-		/* Send Copy Done message */
-		pq_putemptymessage('c');
-	}
-	else
-	{
-		CopySendData(cstate, "\\.", 2);
-		/* Need to flush out the trailer (this also appends a newline) */
-		CopySendEndOfRow(cstate);
-		pq_endcopyout(false);
-	}
+	/* Shouldn't have any unsent data */
+	Assert(cstate->fe_msgbuf->len == 0);
+	/* Send Copy Done message */
+	pq_putemptymessage('c');
 }
 
 /*----------
@@ -268,20 +240,7 @@ CopySendEndOfRow(CopyToState cstate)
 							 errmsg("could not write to COPY file: %m")));
 			}
 			break;
-		case COPY_OLD_FE:
-			/* The FE/BE protocol uses \n as newline for all platforms */
-			if (!cstate->opts.binary)
-				CopySendChar(cstate, '\n');
-
-			if (pq_putbytes(fe_msgbuf->data, fe_msgbuf->len))
-			{
-				/* no hope of recovering connection sync, so FATAL */
-				ereport(FATAL,
-						(errcode(ERRCODE_CONNECTION_FAILURE),
-						 errmsg("connection lost during COPY to stdout")));
-			}
-			break;
-		case COPY_NEW_FE:
+		case COPY_FRONTEND:
 			/* The FE/BE protocol uses \n as newline for all platforms */
 			if (!cstate->opts.binary)
 				CopySendChar(cstate, '\n');
@@ -779,42 +738,6 @@ BeginCopyTo(ParseState *pstate,
 	return cstate;
 }
 
-/*
- * This intermediate routine exists mainly to localize the effects of setjmp
- * so we don't need to plaster a lot of variables with "volatile".
- */
-uint64
-DoCopyTo(CopyToState cstate)
-{
-	bool		pipe = (cstate->filename == NULL);
-	bool		fe_copy = (pipe && whereToSendOutput == DestRemote);
-	uint64		processed;
-
-	PG_TRY();
-	{
-		if (fe_copy)
-			SendCopyBegin(cstate);
-
-		processed = CopyTo(cstate);
-
-		if (fe_copy)
-			SendCopyEnd(cstate);
-	}
-	PG_CATCH();
-	{
-		/*
-		 * Make sure we turn off old-style COPY OUT mode upon error. It is
-		 * okay to do this in all cases, since it does nothing if the mode is
-		 * not on.
-		 */
-		pq_endcopyout(true);
-		PG_RE_THROW();
-	}
-	PG_END_TRY();
-
-	return processed;
-}
-
 /*
  * Clean up storage and release resources for COPY TO.
  */
@@ -837,14 +760,19 @@ EndCopyTo(CopyToState cstate)
 /*
  * Copy from relation or query TO file.
  */
-static uint64
-CopyTo(CopyToState cstate)
+uint64
+DoCopyTo(CopyToState cstate)
 {
+	bool		pipe = (cstate->filename == NULL);
+	bool		fe_copy = (pipe && whereToSendOutput == DestRemote);
 	TupleDesc	tupDesc;
 	int			num_phys_attrs;
 	ListCell   *cur;
 	uint64		processed;
 
+	if (fe_copy)
+		SendCopyBegin(cstate);
+
 	if (cstate->rel)
 		tupDesc = RelationGetDescr(cstate->rel);
 	else
@@ -977,11 +905,14 @@ CopyTo(CopyToState cstate)
 
 	MemoryContextDelete(cstate->rowcontext);
 
+	if (fe_copy)
+		SendCopyEnd(cstate);
+
 	return processed;
 }
 
 /*
- * Emit one row during CopyTo().
+ * Emit one row during DoCopyTo().
  */
 static void
 CopyOneRowTo(CopyToState cstate, TupleTableSlot *slot)
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index 545635f41a9..127f7ea3442 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -653,35 +653,26 @@ static char *
 recv_password_packet(Port *port)
 {
 	StringInfoData buf;
+	int			mtype;
 
 	pq_startmsgread();
-	if (PG_PROTOCOL_MAJOR(port->proto) >= 3)
-	{
-		/* Expect 'p' message type */
-		int			mtype;
 
-		mtype = pq_getbyte();
-		if (mtype != 'p')
-		{
-			/*
-			 * If the client just disconnects without offering a password,
-			 * don't make a log entry.  This is legal per protocol spec and in
-			 * fact commonly done by psql, so complaining just clutters the
-			 * log.
-			 */
-			if (mtype != EOF)
-				ereport(ERROR,
-						(errcode(ERRCODE_PROTOCOL_VIOLATION),
-						 errmsg("expected password response, got message type %d",
-								mtype)));
-			return NULL;		/* EOF or bad message type */
-		}
-	}
-	else
+	/* Expect 'p' message type */
+	mtype = pq_getbyte();
+	if (mtype != 'p')
 	{
-		/* For pre-3.0 clients, avoid log entry if they just disconnect */
-		if (pq_peekbyte() == EOF)
-			return NULL;		/* EOF */
+		/*
+		 * If the client just disconnects without offering a password,
+		 * don't make a log entry.  This is legal per protocol spec and in
+		 * fact commonly done by psql, so complaining just clutters the
+		 * log.
+		 */
+		if (mtype != EOF)
+			ereport(ERROR,
+					(errcode(ERRCODE_PROTOCOL_VIOLATION),
+					 errmsg("expected password response, got message type %d",
+							mtype)));
+		return NULL;		/* EOF or bad message type */
 	}
 
 	initStringInfo(&buf);
@@ -879,19 +870,6 @@ CheckSCRAMAuth(Port *port, char *shadow_pass, char **logdetail)
 	int			result;
 	bool		initial;
 
-	/*
-	 * SASL auth is not supported for protocol versions before 3, because it
-	 * relies on the overall message length word to determine the SASL payload
-	 * size in AuthenticationSASLContinue and PasswordMessage messages.  (We
-	 * used to have a hard rule that protocol messages must be parsable
-	 * without relying on the length word, but we hardly care about older
-	 * protocol version anymore.)
-	 */
-	if (PG_PROTOCOL_MAJOR(FrontendProtocol) < 3)
-		ereport(FATAL,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("SASL authentication is not supported in protocol version 2")));
-
 	/*
 	 * Send the SASL authentication request to user.  It includes the list of
 	 * authentication mechanisms that are supported.
@@ -1041,19 +1019,6 @@ pg_GSS_recvauth(Port *port)
 	StringInfoData buf;
 	gss_buffer_desc gbuf;
 
-	/*
-	 * GSS auth is not supported for protocol versions before 3, because it
-	 * relies on the overall message length word to determine the GSS payload
-	 * size in AuthenticationGSSContinue and PasswordMessage messages. (This
-	 * is, in fact, a design error in our GSS support, because protocol
-	 * messages are supposed to be parsable without relying on the length
-	 * word; but it's not worth changing it now.)
-	 */
-	if (PG_PROTOCOL_MAJOR(FrontendProtocol) < 3)
-		ereport(FATAL,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("GSSAPI is not supported in protocol version 2")));
-
 	/*
 	 * Use the configured keytab, if there is one.  Unfortunately, Heimdal
 	 * doesn't support the cred store extensions, so use the env var.
@@ -1323,19 +1288,6 @@ pg_SSPI_recvauth(Port *port)
 
 	QUERY_SECURITY_CONTEXT_TOKEN_FN _QuerySecurityContextToken;
 
-	/*
-	 * SSPI auth is not supported for protocol versions before 3, because it
-	 * relies on the overall message length word to determine the SSPI payload
-	 * size in AuthenticationGSSContinue and PasswordMessage messages. (This
-	 * is, in fact, a design error in our SSPI support, because protocol
-	 * messages are supposed to be parsable without relying on the length
-	 * word; but it's not worth changing it now.)
-	 */
-	if (PG_PROTOCOL_MAJOR(FrontendProtocol) < 3)
-		ereport(FATAL,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("SSPI is not supported in protocol version 2")));
-
 	/*
 	 * Acquire a handle to the server credentials.
 	 */
diff --git a/src/backend/libpq/pqcomm.c b/src/backend/libpq/pqcomm.c
index 1e6b6db5400..176aa289f5d 100644
--- a/src/backend/libpq/pqcomm.c
+++ b/src/backend/libpq/pqcomm.c
@@ -5,23 +5,13 @@
  *
  * These routines handle the low-level details of communication between
  * frontend and backend.  They just shove data across the communication
- * channel, and are ignorant of the semantics of the data --- or would be,
- * except for major brain damage in the design of the old COPY OUT protocol.
- * Unfortunately, COPY OUT was designed to commandeer the communication
- * channel (it just transfers data without wrapping it into messages).
- * No other messages can be sent while COPY OUT is in progress; and if the
- * copy is aborted by an ereport(ERROR), we need to close out the copy so that
- * the frontend gets back into sync.  Therefore, these routines have to be
- * aware of COPY OUT state.  (New COPY-OUT is message-based and does *not*
- * set the DoingCopyOut flag.)
+ * channel, and are ignorant of the semantics of the data.
  *
- * NOTE: generally, it's a bad idea to emit outgoing messages directly with
- * pq_putbytes(), especially if the message would require multiple calls
- * to send.  Instead, use the routines in pqformat.c to construct the message
- * in a buffer and then emit it in one call to pq_putmessage.  This ensures
- * that the channel will not be clogged by an incomplete message if execution
- * is aborted by ereport(ERROR) partway through the message.  The only
- * non-libpq code that should call pq_putbytes directly is old-style COPY OUT.
+ * To emit an outgoing message, use the routines in pqformat.c to construct
+ * the message in a buffer and then emit it in one call to pq_putmessage.
+ * There are no functions to send raw bytes or partial messages; this
+ * ensures that the channel will not be clogged by an incomplete message if
+ * execution is aborted by ereport(ERROR) partway through the message.
  *
  * At one time, libpq was shared between frontend and backend, but now
  * the backend's "backend/libpq" is quite separate from "interfaces/libpq".
@@ -49,20 +39,16 @@
  *
  * low-level I/O:
  *		pq_getbytes		- get a known number of bytes from connection
- *		pq_getstring	- get a null terminated string from connection
  *		pq_getmessage	- get a message with length word from connection
  *		pq_getbyte		- get next byte from connection
  *		pq_peekbyte		- peek at next byte from connection
- *		pq_putbytes		- send bytes to connection (not flushed until pq_flush)
  *		pq_flush		- flush pending output
  *		pq_flush_if_writable - flush pending output if writable without blocking
  *		pq_getbyte_if_available - get a byte if available without blocking
  *
- * message-level I/O (and old-style-COPY-OUT cruft):
+ * message-level I/O
  *		pq_putmessage	- send a normal message (suppressed in COPY OUT mode)
  *		pq_putmessage_noblock - buffer a normal message (suppressed in COPY OUT)
- *		pq_startcopyout - inform libpq that a COPY OUT transfer is beginning
- *		pq_endcopyout	- end a COPY OUT transfer
  *
  *------------------------
  */
@@ -146,7 +132,6 @@ static int	PqRecvLength;		/* End of data available in PqRecvBuffer */
  */
 static bool PqCommBusy;			/* busy sending data to the client */
 static bool PqCommReadingMsg;	/* in the middle of reading a message */
-static bool DoingCopyOut;		/* in old-protocol COPY OUT processing */
 
 
 /* Internal functions */
@@ -158,8 +143,6 @@ static int	socket_flush_if_writable(void);
 static bool socket_is_send_pending(void);
 static int	socket_putmessage(char msgtype, const char *s, size_t len);
 static void socket_putmessage_noblock(char msgtype, const char *s, size_t len);
-static void socket_startcopyout(void);
-static void socket_endcopyout(bool errorAbort);
 static int	internal_putbytes(const char *s, size_t len);
 static int	internal_flush(void);
 
@@ -174,9 +157,7 @@ static const PQcommMethods PqCommSocketMethods = {
 	socket_flush_if_writable,
 	socket_is_send_pending,
 	socket_putmessage,
-	socket_putmessage_noblock,
-	socket_startcopyout,
-	socket_endcopyout
+	socket_putmessage_noblock
 };
 
 const PQcommMethods *PqCommMethods = &PqCommSocketMethods;
@@ -197,7 +178,6 @@ pq_init(void)
 	PqSendPointer = PqSendStart = PqRecvPointer = PqRecvLength = 0;
 	PqCommBusy = false;
 	PqCommReadingMsg = false;
-	DoingCopyOut = false;
 
 	/* set up process-exit hook to close the socket */
 	on_proc_exit(socket_close, 0);
@@ -238,8 +218,6 @@ socket_comm_reset(void)
 {
 	/* Do not throw away pending data, but do reset the busy flag */
 	PqCommBusy = false;
-	/* We can abort any old-style COPY OUT, too */
-	pq_endcopyout(true);
 }
 
 /* --------------------------------
@@ -1146,58 +1124,6 @@ pq_discardbytes(size_t len)
 	return 0;
 }
 
-/* --------------------------------
- *		pq_getstring	- get a null terminated string from connection
- *
- *		The return value is placed in an expansible StringInfo, which has
- *		already been initialized by the caller.
- *
- *		This is used only for dealing with old-protocol clients.  The idea
- *		is to produce a StringInfo that looks the same as we would get from
- *		pq_getmessage() with a newer client; we will then process it with
- *		pq_getmsgstring.  Therefore, no character set conversion is done here,
- *		even though this is presumably useful only for text.
- *
- *		returns 0 if OK, EOF if trouble
- * --------------------------------
- */
-int
-pq_getstring(StringInfo s)
-{
-	int			i;
-
-	Assert(PqCommReadingMsg);
-
-	resetStringInfo(s);
-
-	/* Read until we get the terminating '\0' */
-	for (;;)
-	{
-		while (PqRecvPointer >= PqRecvLength)
-		{
-			if (pq_recvbuf())	/* If nothing in buffer, then recv some */
-				return EOF;		/* Failed to recv data */
-		}
-
-		for (i = PqRecvPointer; i < PqRecvLength; i++)
-		{
-			if (PqRecvBuffer[i] == '\0')
-			{
-				/* include the '\0' in the copy */
-				appendBinaryStringInfo(s, PqRecvBuffer + PqRecvPointer,
-									   i - PqRecvPointer + 1);
-				PqRecvPointer = i + 1;	/* advance past \0 */
-				return 0;
-			}
-		}
-
-		/* If we're here we haven't got the \0 in the buffer yet. */
-		appendBinaryStringInfo(s, PqRecvBuffer + PqRecvPointer,
-							   PqRecvLength - PqRecvPointer);
-		PqRecvPointer = PqRecvLength;
-	}
-}
-
 
 /* --------------------------------
  *		pq_startmsgread - begin reading a message from the client.
@@ -1224,9 +1150,9 @@ pq_startmsgread(void)
 /* --------------------------------
  *		pq_endmsgread	- finish reading message.
  *
- *		This must be called after reading a V2 protocol message with
- *		pq_getstring() and friends, to indicate that we have read the whole
- *		message. In V3 protocol, pq_getmessage() does this implicitly.
+ *		This must be called after reading a message with pq_getbytes()
+ *		and friends, to indicate that we have read the whole message.
+ *		pq_getmessage() does this implicitly.
  * --------------------------------
  */
 void
@@ -1342,28 +1268,6 @@ pq_getmessage(StringInfo s, int maxlen)
 }
 
 
-/* --------------------------------
- *		pq_putbytes		- send bytes to connection (not flushed until pq_flush)
- *
- *		returns 0 if OK, EOF if trouble
- * --------------------------------
- */
-int
-pq_putbytes(const char *s, size_t len)
-{
-	int			res;
-
-	/* Should only be called by old-style COPY OUT */
-	Assert(DoingCopyOut);
-	/* No-op if reentrant call */
-	if (PqCommBusy)
-		return 0;
-	PqCommBusy = true;
-	res = internal_putbytes(s, len);
-	PqCommBusy = false;
-	return res;
-}
-
 static int
 internal_putbytes(const char *s, size_t len)
 {
@@ -1524,8 +1428,6 @@ socket_is_send_pending(void)
 
 /* --------------------------------
  * Message-level I/O routines begin here.
- *
- * These routines understand about the old-style COPY OUT protocol.
  * --------------------------------
  */
 
@@ -1533,20 +1435,13 @@ socket_is_send_pending(void)
 /* --------------------------------
  *		socket_putmessage - send a normal message (suppressed in COPY OUT mode)
  *
- *		If msgtype is not '\0', it is a message type code to place before
- *		the message body.  If msgtype is '\0', then the message has no type
- *		code (this is only valid in pre-3.0 protocols).
- *
- *		len is the length of the message body data at *s.  In protocol 3.0
- *		and later, a message length word (equal to len+4 because it counts
- *		itself too) is inserted by this routine.
+ *		msgtype is a message type code to place before the message body.
  *
- *		All normal messages are suppressed while old-style COPY OUT is in
- *		progress.  (In practice only a few notice messages might get emitted
- *		then; dropping them is annoying, but at least they will still appear
- *		in the postmaster log.)
+ *		len is the length of the message body data at *s.  A message length
+ *		word (equal to len+4 because it counts itself too) is inserted by this
+ *		routine.
  *
- *		We also suppress messages generated while pqcomm.c is busy.  This
+ *		We suppress messages generated while pqcomm.c is busy.  This
  *		avoids any possibility of messages being inserted within other
  *		messages.  The only known trouble case arises if SIGQUIT occurs
  *		during a pqcomm.c routine --- quickdie() will try to send a warning
@@ -1558,20 +1453,20 @@ socket_is_send_pending(void)
 static int
 socket_putmessage(char msgtype, const char *s, size_t len)
 {
-	if (DoingCopyOut || PqCommBusy)
+	uint32		n32;
+
+	Assert(msgtype != 0);
+
+	if (PqCommBusy)
 		return 0;
 	PqCommBusy = true;
-	if (msgtype)
-		if (internal_putbytes(&msgtype, 1))
-			goto fail;
-	if (PG_PROTOCOL_MAJOR(FrontendProtocol) >= 3)
-	{
-		uint32		n32;
+	if (internal_putbytes(&msgtype, 1))
+		goto fail;
+
+	n32 = pg_hton32((uint32) (len + 4));
+	if (internal_putbytes((char *) &n32, 4))
+		goto fail;
 
-		n32 = pg_hton32((uint32) (len + 4));
-		if (internal_putbytes((char *) &n32, 4))
-			goto fail;
-	}
 	if (internal_putbytes(s, len))
 		goto fail;
 	PqCommBusy = false;
@@ -1609,37 +1504,41 @@ socket_putmessage_noblock(char msgtype, const char *s, size_t len)
 								 * buffer */
 }
 
-
-/* --------------------------------
- *		socket_startcopyout - inform libpq that an old-style COPY OUT transfer
- *			is beginning
- * --------------------------------
- */
-static void
-socket_startcopyout(void)
-{
-	DoingCopyOut = true;
-}
-
 /* --------------------------------
- *		socket_endcopyout	- end an old-style COPY OUT transfer
+ *		pq_putmessage_v2 - send a message in protocol version 2
+ *
+ *		msgtype is a message type code to place before the message body.
  *
- *		If errorAbort is indicated, we are aborting a COPY OUT due to an error,
- *		and must send a terminator line.  Since a partial data line might have
- *		been emitted, send a couple of newlines first (the first one could
- *		get absorbed by a backslash...)  Note that old-style COPY OUT does
- *		not allow binary transfers, so a textual terminator is always correct.
+ *		We no longer support protocol version 2, but we have kept this
+ *		function so that if a client tries to connect with protocol version 2,
+ *		as a courtesy we can still send the "unsupported protocol version"
+ *		error to the client in the old format.
+ *
+ *		Like in pq_putmessage(), we suppress messages generated while
+ *		pqcomm.c is busy.
+ *
+ *		returns 0 if OK, EOF if trouble
  * --------------------------------
  */
-static void
-socket_endcopyout(bool errorAbort)
+int
+pq_putmessage_v2(char msgtype, const char *s, size_t len)
 {
-	if (!DoingCopyOut)
-		return;
-	if (errorAbort)
-		pq_putbytes("\n\n\\.\n", 5);
-	/* in non-error case, copyto.c will have emitted the terminator line */
-	DoingCopyOut = false;
+	Assert(msgtype != 0);
+
+	if (PqCommBusy)
+		return 0;
+	PqCommBusy = true;
+	if (internal_putbytes(&msgtype, 1))
+		goto fail;
+
+	if (internal_putbytes(s, len))
+		goto fail;
+	PqCommBusy = false;
+	return 0;
+
+fail:
+	PqCommBusy = false;
+	return EOF;
 }
 
 /*
diff --git a/src/backend/libpq/pqmq.c b/src/backend/libpq/pqmq.c
index f468441b7a2..d1a1f47a788 100644
--- a/src/backend/libpq/pqmq.c
+++ b/src/backend/libpq/pqmq.c
@@ -33,8 +33,6 @@ static int	mq_flush_if_writable(void);
 static bool mq_is_send_pending(void);
 static int	mq_putmessage(char msgtype, const char *s, size_t len);
 static void mq_putmessage_noblock(char msgtype, const char *s, size_t len);
-static void mq_startcopyout(void);
-static void mq_endcopyout(bool errorAbort);
 
 static const PQcommMethods PqCommMqMethods = {
 	mq_comm_reset,
@@ -42,9 +40,7 @@ static const PQcommMethods PqCommMqMethods = {
 	mq_flush_if_writable,
 	mq_is_send_pending,
 	mq_putmessage,
-	mq_putmessage_noblock,
-	mq_startcopyout,
-	mq_endcopyout
+	mq_putmessage_noblock
 };
 
 /*
@@ -195,18 +191,6 @@ mq_putmessage_noblock(char msgtype, const char *s, size_t len)
 	elog(ERROR, "not currently supported");
 }
 
-static void
-mq_startcopyout(void)
-{
-	/* Nothing to do. */
-}
-
-static void
-mq_endcopyout(bool errorAbort)
-{
-	/* Nothing to do. */
-}
-
 /*
  * Parse an ErrorResponse or NoticeResponse payload and populate an ErrorData
  * structure with the results.
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index 7de27ee4e01..9f4c877f6e5 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -1930,7 +1930,7 @@ static int
 ProcessStartupPacket(Port *port, bool ssl_done, bool gss_done)
 {
 	int32		len;
-	void	   *buf;
+	char	   *buf;
 	ProtocolVersion proto;
 	MemoryContext oldcontext;
 
@@ -1980,15 +1980,12 @@ ProcessStartupPacket(Port *port, bool ssl_done, bool gss_done)
 	}
 
 	/*
-	 * Allocate at least the size of an old-style startup packet, plus one
-	 * extra byte, and make sure all are zeroes.  This ensures we will have
-	 * null termination of all strings, in both fixed- and variable-length
-	 * packet layouts.
+	 * Allocate space to hold the startup packet, plus one extra byte that's
+	 * initialized to be zero.  This ensures we will have null termination of
+	 * all strings inside the packet.
 	 */
-	if (len <= (int32) sizeof(StartupPacket))
-		buf = palloc0(sizeof(StartupPacket) + 1);
-	else
-		buf = palloc0(len + 1);
+	buf = palloc(len + 1);
+	buf[len] = '\0';
 
 	if (pq_getbytes(buf, len) == EOF)
 	{
@@ -2111,7 +2108,7 @@ retry1:
 	 */
 	oldcontext = MemoryContextSwitchTo(TopMemoryContext);
 
-	if (PG_PROTOCOL_MAJOR(proto) >= 3)
+	/* Handle protocol version 3 startup packet */
 	{
 		int32		offset = sizeof(ProtocolVersion);
 		List	   *unrecognized_protocol_options = NIL;
@@ -2125,7 +2122,7 @@ retry1:
 
 		while (offset < len)
 		{
-			char	   *nameptr = ((char *) buf) + offset;
+			char	   *nameptr = buf + offset;
 			int32		valoffset;
 			char	   *valptr;
 
@@ -2134,7 +2131,7 @@ retry1:
 			valoffset = offset + strlen(nameptr) + 1;
 			if (valoffset >= len)
 				break;			/* missing value, will complain below */
-			valptr = ((char *) buf) + valoffset;
+			valptr = buf + valoffset;
 
 			if (strcmp(nameptr, "database") == 0)
 				port->database_name = pstrdup(valptr);
@@ -2219,27 +2216,6 @@ retry1:
 			unrecognized_protocol_options != NIL)
 			SendNegotiateProtocolVersion(unrecognized_protocol_options);
 	}
-	else
-	{
-		/*
-		 * Get the parameters from the old-style, fixed-width-fields startup
-		 * packet as C strings.  The packet destination was cleared first so a
-		 * short packet has zeros silently added.  We have to be prepared to
-		 * truncate the pstrdup result for oversize fields, though.
-		 */
-		StartupPacket *packet = (StartupPacket *) buf;
-
-		port->database_name = pstrdup(packet->database);
-		if (strlen(port->database_name) > sizeof(packet->database))
-			port->database_name[sizeof(packet->database)] = '\0';
-		port->user_name = pstrdup(packet->user);
-		if (strlen(port->user_name) > sizeof(packet->user))
-			port->user_name[sizeof(packet->user)] = '\0';
-		port->cmdline_options = pstrdup(packet->options);
-		if (strlen(port->cmdline_options) > sizeof(packet->options))
-			port->cmdline_options[sizeof(packet->options)] = '\0';
-		port->guc_options = NIL;
-	}
 
 	/* Check a user name was given. */
 	if (port->user_name == NULL || port->user_name[0] == '\0')
diff --git a/src/backend/tcop/dest.c b/src/backend/tcop/dest.c
index 4316137a9d3..1dfadfa8e1d 100644
--- a/src/backend/tcop/dest.c
+++ b/src/backend/tcop/dest.c
@@ -226,13 +226,8 @@ EndReplicationCommand(const char *commandTag)
 /* ----------------
  *		NullCommand - tell dest that an empty query string was recognized
  *
- *		In FE/BE protocol version 1.0, this hack is necessary to support
- *		libpq's crufty way of determining whether a multiple-command
- *		query string is done.  In protocol 2.0 it's probably not really
- *		necessary to distinguish empty queries anymore, but we still do it
- *		for backwards compatibility with 1.0.  In protocol 3.0 it has some
- *		use again, since it ensures that there will be a recognizable end
- *		to the response to an Execute message.
+ *		This ensures that there will be a recognizable end to the response
+ *		to an Execute message in the extended query protocol.
  * ----------------
  */
 void
@@ -244,14 +239,8 @@ NullCommand(CommandDest dest)
 		case DestRemoteExecute:
 		case DestRemoteSimple:
 
-			/*
-			 * tell the fe that we saw an empty query string.  In protocols
-			 * before 3.0 this has a useless empty-string message body.
-			 */
-			if (PG_PROTOCOL_MAJOR(FrontendProtocol) >= 3)
-				pq_putemptymessage('I');
-			else
-				pq_putmessage('I', "", 1);
+			/* Tell the FE that we saw an empty query string */
+			pq_putemptymessage('I');
 			break;
 
 		case DestNone:
@@ -286,7 +275,6 @@ ReadyForQuery(CommandDest dest)
 		case DestRemote:
 		case DestRemoteExecute:
 		case DestRemoteSimple:
-			if (PG_PROTOCOL_MAJOR(FrontendProtocol) >= 3)
 			{
 				StringInfoData buf;
 
@@ -294,8 +282,6 @@ ReadyForQuery(CommandDest dest)
 				pq_sendbyte(&buf, TransactionBlockStatusCode());
 				pq_endmessage(&buf);
 			}
-			else
-				pq_putemptymessage('Z');
 			/* Flush output at end of cycle in any case. */
 			pq_flush();
 			break;
diff --git a/src/backend/tcop/fastpath.c b/src/backend/tcop/fastpath.c
index 1b76653caa4..77d17ebca94 100644
--- a/src/backend/tcop/fastpath.c
+++ b/src/backend/tcop/fastpath.c
@@ -58,98 +58,24 @@ struct fp_info
 
 static int16 parse_fcall_arguments(StringInfo msgBuf, struct fp_info *fip,
 								   FunctionCallInfo fcinfo);
-static int16 parse_fcall_arguments_20(StringInfo msgBuf, struct fp_info *fip,
-									  FunctionCallInfo fcinfo);
-
-
-/* ----------------
- *		GetOldFunctionMessage
- *
- * In pre-3.0 protocol, there is no length word on the message, so we have
- * to have code that understands the message layout to absorb the message
- * into a buffer.  We want to do this before we start execution, so that
- * we do not lose sync with the frontend if there's an error.
- *
- * The caller should already have initialized buf to empty.
- * ----------------
- */
-int
-GetOldFunctionMessage(StringInfo buf)
-{
-	int32		ibuf;
-	int			nargs;
-
-	/* Dummy string argument */
-	if (pq_getstring(buf))
-		return EOF;
-	/* Function OID */
-	if (pq_getbytes((char *) &ibuf, 4))
-		return EOF;
-	appendBinaryStringInfo(buf, (char *) &ibuf, 4);
-	/* Number of arguments */
-	if (pq_getbytes((char *) &ibuf, 4))
-		return EOF;
-	appendBinaryStringInfo(buf, (char *) &ibuf, 4);
-	nargs = pg_ntoh32(ibuf);
-	/* For each argument ... */
-	while (nargs-- > 0)
-	{
-		int			argsize;
-
-		/* argsize */
-		if (pq_getbytes((char *) &ibuf, 4))
-			return EOF;
-		appendBinaryStringInfo(buf, (char *) &ibuf, 4);
-		argsize = pg_ntoh32(ibuf);
-		if (argsize < -1)
-		{
-			/* FATAL here since no hope of regaining message sync */
-			ereport(FATAL,
-					(errcode(ERRCODE_PROTOCOL_VIOLATION),
-					 errmsg("invalid argument size %d in function call message",
-							argsize)));
-		}
-		/* and arg contents */
-		if (argsize > 0)
-		{
-			/* Allocate space for arg */
-			enlargeStringInfo(buf, argsize);
-			/* And grab it */
-			if (pq_getbytes(buf->data + buf->len, argsize))
-				return EOF;
-			buf->len += argsize;
-			/* Place a trailing null per StringInfo convention */
-			buf->data[buf->len] = '\0';
-		}
-	}
-	return 0;
-}
 
 /* ----------------
  *		SendFunctionResult
- *
- * Note: although this routine doesn't check, the format had better be 1
- * (binary) when talking to a pre-3.0 client.
  * ----------------
  */
 static void
 SendFunctionResult(Datum retval, bool isnull, Oid rettype, int16 format)
 {
-	bool		newstyle = (PG_PROTOCOL_MAJOR(FrontendProtocol) >= 3);
 	StringInfoData buf;
 
 	pq_beginmessage(&buf, 'V');
 
 	if (isnull)
 	{
-		if (newstyle)
-			pq_sendint32(&buf, -1);
+		pq_sendint32(&buf, -1);
 	}
 	else
 	{
-		if (!newstyle)
-			pq_sendbyte(&buf, 'G');
-
 		if (format == 0)
 		{
 			Oid			typoutput;
@@ -180,9 +106,6 @@ SendFunctionResult(Datum retval, bool isnull, Oid rettype, int16 format)
 					 errmsg("unsupported format code: %d", format)));
 	}
 
-	if (!newstyle)
-		pq_sendbyte(&buf, '0');
-
 	pq_endmessage(&buf);
 }
 
@@ -288,9 +211,6 @@ HandleFunctionRequest(StringInfo msgBuf)
 	/*
 	 * Begin parsing the buffer contents.
 	 */
-	if (PG_PROTOCOL_MAJOR(FrontendProtocol) < 3)
-		(void) pq_getmsgstring(msgBuf); /* dummy string */
-
 	fid = (Oid) pq_getmsgint(msgBuf, 4);	/* function oid */
 
 	/*
@@ -334,10 +254,7 @@ HandleFunctionRequest(StringInfo msgBuf)
 	 */
 	InitFunctionCallInfoData(*fcinfo, &fip->flinfo, 0, InvalidOid, NULL, NULL);
 
-	if (PG_PROTOCOL_MAJOR(FrontendProtocol) >= 3)
-		rformat = parse_fcall_arguments(msgBuf, fip, fcinfo);
-	else
-		rformat = parse_fcall_arguments_20(msgBuf, fip, fcinfo);
+	rformat = parse_fcall_arguments(msgBuf, fip, fcinfo);
 
 	/* Verify we reached the end of the message where expected. */
 	pq_getmsgend(msgBuf);
@@ -533,81 +450,3 @@ parse_fcall_arguments(StringInfo msgBuf, struct fp_info *fip,
 	/* Return result format code */
 	return (int16) pq_getmsgint(msgBuf, 2);
 }
-
-/*
- * Parse function arguments in a 2.0 protocol message
- *
- * Argument values are loaded into *fcinfo, and the desired result format
- * is returned.
- */
-static int16
-parse_fcall_arguments_20(StringInfo msgBuf, struct fp_info *fip,
-						 FunctionCallInfo fcinfo)
-{
-	int			nargs;
-	int			i;
-	StringInfoData abuf;
-
-	nargs = pq_getmsgint(msgBuf, 4);	/* # of arguments */
-
-	if (fip->flinfo.fn_nargs != nargs || nargs > FUNC_MAX_ARGS)
-		ereport(ERROR,
-				(errcode(ERRCODE_PROTOCOL_VIOLATION),
-				 errmsg("function call message contains %d arguments but function requires %d",
-						nargs, fip->flinfo.fn_nargs)));
-
-	fcinfo->nargs = nargs;
-
-	initStringInfo(&abuf);
-
-	/*
-	 * Copy supplied arguments into arg vector.  In protocol 2.0 these are
-	 * always assumed to be supplied in binary format.
-	 *
-	 * Note: although the original protocol 2.0 code did not have any way for
-	 * the frontend to specify a NULL argument, we now choose to interpret
-	 * length == -1 as meaning a NULL.
-	 */
-	for (i = 0; i < nargs; ++i)
-	{
-		int			argsize;
-		Oid			typreceive;
-		Oid			typioparam;
-
-		getTypeBinaryInputInfo(fip->argtypes[i], &typreceive, &typioparam);
-
-		argsize = pq_getmsgint(msgBuf, 4);
-		if (argsize == -1)
-		{
-			fcinfo->args[i].isnull = true;
-			fcinfo->args[i].value = OidReceiveFunctionCall(typreceive, NULL,
-														   typioparam, -1);
-			continue;
-		}
-		fcinfo->args[i].isnull = false;
-		if (argsize < 0)
-			ereport(ERROR,
-					(errcode(ERRCODE_PROTOCOL_VIOLATION),
-					 errmsg("invalid argument size %d in function call message",
-							argsize)));
-
-		/* Reset abuf to empty, and insert raw data into it */
-		resetStringInfo(&abuf);
-		appendBinaryStringInfo(&abuf,
-							   pq_getmsgbytes(msgBuf, argsize),
-							   argsize);
-
-		fcinfo->args[i].value = OidReceiveFunctionCall(typreceive, &abuf,
-													   typioparam, -1);
-
-		/* Trouble if it didn't eat the whole buffer */
-		if (abuf.cursor != abuf.len)
-			ereport(ERROR,
-					(errcode(ERRCODE_INVALID_BINARY_REPRESENTATION),
-					 errmsg("incorrect binary data format in function argument %d",
-							i + 1)));
-	}
-
-	/* Desired result format is always binary in protocol 2.0 */
-	return 1;
-}
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index cb5a96117f6..fcf6161da3f 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -370,57 +370,10 @@ SocketBackend(StringInfo inBuf)
 	{
 		case 'Q':				/* simple query */
 			doing_extended_query_message = false;
-			if (PG_PROTOCOL_MAJOR(FrontendProtocol) < 3)
-			{
-				/* old style without length word; convert */
-				if (pq_getstring(inBuf))
-				{
-					if (IsTransactionState())
-						ereport(COMMERROR,
-								(errcode(ERRCODE_CONNECTION_FAILURE),
-								 errmsg("unexpected EOF on client connection with an open transaction")));
-					else
-					{
-						/*
-						 * Can't send DEBUG log messages to client at this
-						 * point. Since we're disconnecting right away, we
-						 * don't need to restore whereToSendOutput.
-						 */
-						whereToSendOutput = DestNone;
-						ereport(DEBUG1,
-								(errcode(ERRCODE_CONNECTION_DOES_NOT_EXIST),
-								 errmsg("unexpected EOF on client connection")));
-					}
-					return EOF;
-				}
-			}
 			break;
 
 		case 'F':				/* fastpath function call */
 			doing_extended_query_message = false;
-			if (PG_PROTOCOL_MAJOR(FrontendProtocol) < 3)
-			{
-				if (GetOldFunctionMessage(inBuf))
-				{
-					if (IsTransactionState())
-						ereport(COMMERROR,
-								(errcode(ERRCODE_CONNECTION_FAILURE),
-								 errmsg("unexpected EOF on client connection with an open transaction")));
-					else
-					{
-						/*
-						 * Can't send DEBUG log messages to client at this
-						 * point. Since we're disconnecting right away, we
-						 * don't need to restore whereToSendOutput.
-						 */
-						whereToSendOutput = DestNone;
-						ereport(DEBUG1,
-								(errcode(ERRCODE_CONNECTION_DOES_NOT_EXIST),
-								 errmsg("unexpected EOF on client connection")));
-					}
-					return EOF;
-				}
-			}
 			break;
 
 		case 'X':				/* terminate */
@@ -435,11 +388,6 @@ SocketBackend(StringInfo inBuf)
 		case 'H':				/* flush */
 		case 'P':				/* parse */
 			doing_extended_query_message = true;
-			/* these are only legal in protocol 3 */
-			if (PG_PROTOCOL_MAJOR(FrontendProtocol) < 3)
-				ereport(FATAL,
-						(errcode(ERRCODE_PROTOCOL_VIOLATION),
-						 errmsg("invalid frontend message type %d", qtype)));
 			break;
 
 		case 'S':				/* sync */
@@ -447,22 +395,12 @@ SocketBackend(StringInfo inBuf)
 			ignore_till_sync = false;
 			/* mark not-extended, so that a new error doesn't begin skip */
 			doing_extended_query_message = false;
-			/* only legal in protocol 3 */
-			if (PG_PROTOCOL_MAJOR(FrontendProtocol) < 3)
-				ereport(FATAL,
-						(errcode(ERRCODE_PROTOCOL_VIOLATION),
-						 errmsg("invalid frontend message type %d", qtype)));
 			break;
 
 		case 'd':				/* copy data */
 		case 'c':				/* copy done */
 		case 'f':				/* copy fail */
 			doing_extended_query_message = false;
-			/* these are only legal in protocol 3 */
-			if (PG_PROTOCOL_MAJOR(FrontendProtocol) < 3)
-				ereport(FATAL,
-						(errcode(ERRCODE_PROTOCOL_VIOLATION),
-						 errmsg("invalid frontend message type %d", qtype)));
 			break;
 
 		default:
@@ -483,13 +421,8 @@ SocketBackend(StringInfo inBuf)
 	 * after the type code; we can read the message contents independently of
 	 * the type.
 	 */
-	if (PG_PROTOCOL_MAJOR(FrontendProtocol) >= 3)
-	{
-		if (pq_getmessage(inBuf, 0))
-			return EOF;			/* suitable message already logged */
-	}
-	else
-		pq_endmsgread();
+	if (pq_getmessage(inBuf, 0))
+		return EOF;			/* suitable message already logged */
 	RESUME_CANCEL_INTERRUPTS();
 
 	return qtype;
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 80c26724612..e729ebece7b 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -589,16 +589,6 @@ errfinish(const char *filename, int lineno, const char *funcname)
 		PG_RE_THROW();
 	}
 
-	/*
-	 * If we are doing FATAL or PANIC, abort any old-style COPY OUT in
-	 * progress, so that we can report the message before dying.  (Without
-	 * this, pq_putmessage will refuse to send the message at all, which is
-	 * what we want for NOTICE messages, but not for fatal exits.) This hack
-	 * is necessary because of poor design of old-style copy protocol.
-	 */
-	if (elevel >= FATAL && whereToSendOutput == DestRemote)
-		pq_endcopyout(true);
-
 	/* Emit the message to the right places */
 	EmitErrorReport();
 
@@ -1261,28 +1251,6 @@ errhidecontext(bool hide_ctx)
 	return 0;					/* return value does not matter */
 }
 
-
-/*
- * errfunction --- add reporting function name to the current error
- *
- * This is used when backwards compatibility demands that the function
- * name appear in messages sent to old-protocol clients.  Note that the
- * passed string is expected to be a non-freeable constant string.
- */
-int
-errfunction(const char *funcname)
-{
-	ErrorData  *edata = &errordata[errordata_stack_depth];
-
-	/* we don't bother incrementing recursion_depth */
-	CHECK_STACK_DEPTH();
-
-	edata->funcname = funcname;
-	edata->show_funcname = true;
-
-	return 0;					/* return value does not matter */
-}
-
 /*
  * errposition --- add cursor position to the current error
  */
@@ -3291,10 +3259,14 @@ send_message_to_frontend(ErrorData *edata)
 {
 	StringInfoData msgbuf;
 
-	/* 'N' (Notice) is for nonfatal conditions, 'E' is for errors */
-	pq_beginmessage(&msgbuf, (edata->elevel < ERROR) ? 'N' : 'E');
-
-	if (PG_PROTOCOL_MAJOR(FrontendProtocol) >= 3)
+	/*
+	 * We no longer support pre-3.0 FE/BE protocol, except here.  If a client
+	 * tries to connect using an older protocol version, it's nice to send the
+	 * "protocol version not supported" error in a format the client
+	 * understands.  If protocol hasn't been set yet, early in backend
+	 * startup, assume modern protocol.
+	 */
+	if (PG_PROTOCOL_MAJOR(FrontendProtocol) >= 3 || FrontendProtocol == 0)
 	{
 		/* New style with separate fields */
 		const char *sev;
@@ -3302,6 +3274,9 @@ send_message_to_frontend(ErrorData *edata)
 		int			ssval;
 		int			i;
 
+		/* 'N' (Notice) is for nonfatal conditions, 'E' is for errors */
+		pq_beginmessage(&msgbuf, (edata->elevel < ERROR) ? 'N' : 'E');
+
 		sev = error_severity(edata->elevel);
 		pq_sendbyte(&msgbuf, PG_DIAG_SEVERITY);
 		err_sendstring(&msgbuf, _(sev));
@@ -3417,6 +3392,8 @@ send_message_to_frontend(ErrorData *edata)
 		}
 
 		pq_sendbyte(&msgbuf, '\0'); /* terminator */
+
+		pq_endmessage(&msgbuf);
 	}
 	else
 	{
@@ -3427,30 +3404,19 @@ send_message_to_frontend(ErrorData *edata)
 
 		appendStringInfo(&buf, "%s:  ", _(error_severity(edata->elevel)));
 
-		if (edata->show_funcname && edata->funcname)
-			appendStringInfo(&buf, "%s: ", edata->funcname);
-
 		if (edata->message)
 			appendStringInfoString(&buf, edata->message);
 		else
 			appendStringInfoString(&buf, _("missing error text"));
 
-		if (edata->cursorpos > 0)
-			appendStringInfo(&buf, _(" at character %d"),
-							 edata->cursorpos);
-		else if (edata->internalpos > 0)
-			appendStringInfo(&buf, _(" at character %d"),
-							 edata->internalpos);
-
 		appendStringInfoChar(&buf, '\n');
 
-		err_sendstring(&msgbuf, buf.data);
+		/* 'N' (Notice) is for nonfatal conditions, 'E' is for errors */
+		pq_putmessage_v2((edata->elevel < ERROR) ? 'N' : 'E', buf.data, buf.len + 1);
 
 		pfree(buf.data);
 	}
 
-	pq_endmessage(&msgbuf);
-
 	/*
 	 * This flush is normally not necessary, since postgres.c will flush out
 	 * waiting data when control returns to the main loop. But it seems best
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index eafdb1118ed..34b2a2a871e 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -6295,11 +6295,9 @@ BeginReportingGUCOptions(void)
 	int			i;
 
 	/*
-	 * Don't do anything unless talking to an interactive frontend of protocol
-	 * 3.0 or later.
+	 * Don't do anything unless talking to an interactive frontend.
 	 */
-	if (whereToSendOutput != DestRemote ||
-		PG_PROTOCOL_MAJOR(FrontendProtocol) < 3)
+	if (whereToSendOutput != DestRemote)
 		return;
 
 	reporting_enabled = true;
diff --git a/src/bin/psql/common.c b/src/bin/psql/common.c
index 6f507104f46..afd8a9ec151 100644
--- a/src/bin/psql/common.c
+++ b/src/bin/psql/common.c
@@ -2153,9 +2153,6 @@ is_select_command(const char *query)
 
 /*
  * Test if the current user is a database superuser.
- *
- * Note: this will correctly detect superuserness only with a protocol-3.0
- * or newer backend; otherwise it will always say "false".
  */
 bool
 is_superuser(void)
@@ -2176,9 +2173,6 @@ is_superuser(void)
 
 /*
  * Test if the current session uses standard string literals.
- *
- * Note: With a pre-protocol-3.0 connection this will always say "false",
- * which should be the right answer.
  */
 bool
 standard_strings(void)
@@ -2199,10 +2193,6 @@ standard_strings(void)
 
 /*
  * Return the session user of the current connection.
- *
- * Note: this will correctly detect the session user only with a
- * protocol-3.0 or newer backend; otherwise it will return the
- * connection user.
  */
 const char *
 session_username(void)
diff --git a/src/bin/psql/copy.c b/src/bin/psql/copy.c
index 78f0dc5a507..e1fee8e0992 100644
--- a/src/bin/psql/copy.c
+++ b/src/bin/psql/copy.c
@@ -662,7 +662,9 @@ handleCopyIn(PGconn *conn, FILE *copystream, bool isbinary, PGresult **res)
 
 	/*
 	 * Terminate data transfer.  We can't send an error message if we're using
-	 * protocol version 2.
+	 * protocol version 2.  (libpq no longer supports protocol version 2, but
+	 * keep the version checks just in case you're using a pre-v14 libpq.so at
+	 * runtime)
 	 */
 	if (PQputCopyEnd(conn,
 					 (OK || PQprotocolVersion(conn) < 3) ? NULL :
diff --git a/src/include/commands/copyfrom_internal.h b/src/include/commands/copyfrom_internal.h
index e37942df391..705f5b615be 100644
--- a/src/include/commands/copyfrom_internal.h
+++ b/src/include/commands/copyfrom_internal.h
@@ -24,8 +24,7 @@
 typedef enum CopySource
 {
 	COPY_FILE,					/* from file (or a piped program) */
-	COPY_OLD_FE,				/* from frontend (2.0 protocol) */
-	COPY_NEW_FE,				/* from frontend (3.0 protocol) */
+	COPY_FRONTEND,				/* from frontend */
 	COPY_CALLBACK				/* from callback function */
 } CopySource;
 
diff --git a/src/include/libpq/libpq.h b/src/include/libpq/libpq.h
index a55898c85af..42d97c014a2 100644
--- a/src/include/libpq/libpq.h
+++ b/src/include/libpq/libpq.h
@@ -29,8 +29,6 @@ typedef struct
 	bool		(*is_send_pending) (void);
 	int			(*putmessage) (char msgtype, const char *s, size_t len);
 	void		(*putmessage_noblock) (char msgtype, const char *s, size_t len);
-	void		(*startcopyout) (void);
-	void		(*endcopyout) (bool errorAbort);
 } PQcommMethods;
 
 extern const PGDLLIMPORT PQcommMethods *PqCommMethods;
@@ -43,8 +41,6 @@ extern const PGDLLIMPORT PQcommMethods *PqCommMethods;
 	(PqCommMethods->putmessage(msgtype, s, len))
 #define pq_putmessage_noblock(msgtype, s, len) \
 	(PqCommMethods->putmessage_noblock(msgtype, s, len))
-#define pq_startcopyout() (PqCommMethods->startcopyout())
-#define pq_endcopyout(errorAbort) (PqCommMethods->endcopyout(errorAbort))
 
 /*
  * External functions.
@@ -64,7 +60,6 @@ extern void TouchSocketFiles(void);
 extern void RemoveSocketFiles(void);
 extern void pq_init(void);
 extern int	pq_getbytes(char *s, size_t len);
-extern int	pq_getstring(StringInfo s);
 extern void pq_startmsgread(void);
 extern void pq_endmsgread(void);
 extern bool pq_is_reading_msg(void);
@@ -72,7 +67,7 @@ extern int	pq_getmessage(StringInfo s, int maxlen);
 extern int	pq_getbyte(void);
 extern int	pq_peekbyte(void);
 extern int	pq_getbyte_if_available(unsigned char *c);
-extern int	pq_putbytes(const char *s, size_t len);
+extern int	pq_putmessage_v2(char msgtype, const char *s, size_t len);
 
 /*
  * prototypes for functions in be-secure.c
diff --git a/src/include/libpq/pqcomm.h b/src/include/libpq/pqcomm.h
index a86b895b268..be9d9705744 100644
--- a/src/include/libpq/pqcomm.h
+++ b/src/include/libpq/pqcomm.h
@@ -114,9 +114,12 @@ is_unixsock_path(const char *path)
 #define PG_PROTOCOL_MINOR(v)	((v) & 0x0000ffff)
 #define PG_PROTOCOL(m,n)	(((m) << 16) | (n))
 
-/* The earliest and latest frontend/backend protocol version supported. */
+/*
+ * The earliest and latest frontend/backend protocol version supported.
+ * (Only protocol version 3 is currently supported)
+ */
 
-#define PG_PROTOCOL_EARLIEST	PG_PROTOCOL(2,0)
+#define PG_PROTOCOL_EARLIEST	PG_PROTOCOL(3,0)
 #define PG_PROTOCOL_LATEST		PG_PROTOCOL(3,0)
 
 typedef uint32 ProtocolVersion; /* FE/BE protocol version number */
@@ -132,32 +135,6 @@ typedef ProtocolVersion MsgType;
 
 typedef uint32 PacketLen;
 
-
-/*
- * Old-style startup packet layout with fixed-width fields.  This is used in
- * protocol 1.0 and 2.0, but not in later versions.  Note that the fields
- * in this layout are '\0' terminated only if there is room.
- */
-
-#define SM_DATABASE		64
-#define SM_USER			32
-/* We append database name if db_user_namespace true. */
-#define SM_DATABASE_USER (SM_DATABASE+SM_USER+1)	/* +1 for @ */
-#define SM_OPTIONS		64
-#define SM_UNUSED		64
-#define SM_TTY			64
-
-typedef struct StartupPacket
-{
-	ProtocolVersion protoVersion;	/* Protocol version */
-	char		database[SM_DATABASE];	/* Database name */
-	/* Db_user_namespace appends dbname */
-	char		user[SM_USER];	/* User name */
-	char		options[SM_OPTIONS];	/* Optional additional args */
-	char		unused[SM_UNUSED];	/* Unused */
-	char		tty[SM_TTY];	/* Tty for debug output */
-} StartupPacket;
-
 extern bool Db_user_namespace;
 
 /*
diff --git a/src/include/tcop/fastpath.h b/src/include/tcop/fastpath.h
index b67c44fe69e..c4d7a47dd72 100644
--- a/src/include/tcop/fastpath.h
+++ b/src/include/tcop/fastpath.h
@@ -15,7 +15,6 @@
 
 #include "lib/stringinfo.h"
 
-extern int	GetOldFunctionMessage(StringInfo buf);
 extern void HandleFunctionRequest(StringInfo msgBuf);
 
 #endif							/* FASTPATH_H */
diff --git a/src/include/utils/elog.h b/src/include/utils/elog.h
index 3c0e57621fc..b59651289e4 100644
--- a/src/include/utils/elog.h
+++ b/src/include/utils/elog.h
@@ -207,7 +207,6 @@ extern int	errhidecontext(bool hide_ctx);
 
 extern int	errbacktrace(void);
 
-extern int	errfunction(const char *funcname);
 extern int	errposition(int cursorpos);
 
 extern int	internalerrposition(int cursorpos);
@@ -367,7 +366,6 @@ typedef struct ErrorData
 	int			elevel;			/* error level */
 	bool		output_to_server;	/* will report to server log? */
 	bool		output_to_client;	/* will report to client? */
-	bool		show_funcname;	/* true to force funcname inclusion */
 	bool		hide_stmt;		/* true to prevent STATEMENT: inclusion */
 	bool		hide_ctx;		/* true to prevent CONTEXT: inclusion */
 	const char *filename;		/* __FILE__ of ereport() call */
diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile
index f74677eaf9b..2aca882a2be 100644
--- a/src/interfaces/libpq/Makefile
+++ b/src/interfaces/libpq/Makefile
@@ -37,7 +37,6 @@ OBJS = \
 	fe-lobj.o \
 	fe-misc.o \
 	fe-print.o \
-	fe-protocol2.o \
 	fe-protocol3.o \
 	fe-secure.o \
 	legacy-pqsignal.o \
diff --git a/src/interfaces/libpq/fe-auth.c b/src/interfaces/libpq/fe-auth.c
index 168b3df52bf..e8062647e60 100644
--- a/src/interfaces/libpq/fe-auth.c
+++ b/src/interfaces/libpq/fe-auth.c
@@ -579,7 +579,7 @@ pg_SASL_init(PGconn *conn, int payloadlen)
 	/*
 	 * Build a SASLInitialResponse message, and send it.
 	 */
-	if (pqPutMsgStart('p', true, conn))
+	if (pqPutMsgStart('p', conn))
 		goto error;
 	if (pqPuts(selected_mechanism, conn))
 		goto error;
@@ -798,11 +798,7 @@ pg_password_sendauth(PGconn *conn, const char *password, AuthRequest areq)
 		default:
 			return STATUS_ERROR;
 	}
-	/* Packet has a message type as of protocol 3.0 */
-	if (PG_PROTOCOL_MAJOR(conn->pversion) >= 3)
-		ret = pqPacketSend(conn, 'p', pwd_to_send, strlen(pwd_to_send) + 1);
-	else
-		ret = pqPacketSend(conn, 0, pwd_to_send, strlen(pwd_to_send) + 1);
+	ret = pqPacketSend(conn, 'p', pwd_to_send, strlen(pwd_to_send) + 1);
 	if (crypt_pwd)
 		free(crypt_pwd);
 	return ret;
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index 8ca0583aa90..5cd79532f32 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -2263,10 +2263,6 @@ PQconnectPoll(PGconn *conn)
 		case CONNECTION_MADE:
 			break;
 
-			/* We allow pqSetenvPoll to decide whether to proceed. */
-		case CONNECTION_SETENV:
-			break;
-
 			/* Special cases: proceed without waiting. */
 		case CONNECTION_SSL_STARTUP:
 		case CONNECTION_NEEDED:
@@ -2918,12 +2914,8 @@ keep_going:						/* We will come back to here until there is
 				/*
 				 * Build the startup packet.
 				 */
-				if (PG_PROTOCOL_MAJOR(conn->pversion) >= 3)
-					startpacket = pqBuildStartupPacket3(conn, &packetlen,
-														EnvironmentOptions);
-				else
-					startpacket = pqBuildStartupPacket2(conn, &packetlen,
-														EnvironmentOptions);
+				startpacket = pqBuildStartupPacket3(conn, &packetlen,
+													EnvironmentOptions);
 				if (!startpacket)
 				{
 					appendPQExpBufferStr(&conn->errorMessage,
@@ -3209,19 +3201,11 @@ keep_going:						/* We will come back to here until there is
 					goto error_return;
 				}
 
-				if (PG_PROTOCOL_MAJOR(conn->pversion) >= 3)
+				/* Read message length word */
+				if (pqGetInt(&msgLength, 4, conn))
 				{
-					/* Read message length word */
-					if (pqGetInt(&msgLength, 4, conn))
-					{
-						/* We'll come back when there is more data */
-						return PGRES_POLLING_READING;
-					}
-				}
-				else
-				{
-					/* Set phony message length to disable checks below */
-					msgLength = 8;
+					/* We'll come back when there is more data */
+					return PGRES_POLLING_READING;
 				}
 
 				/*
@@ -3230,7 +3214,9 @@ keep_going:						/* We will come back to here until there is
 				 * auth requests may not be that small.  Errors can be a
 				 * little larger, but not huge.  If we see a large apparent
 				 * length in an error, it means we're really talking to a
-				 * pre-3.0-protocol server; cope.
+				 * pre-3.0-protocol server; cope.  (Before version 14, the
+				 * server also used the old protocol for errors that happened
+				 * before processing the startup packet.)
 				 */
 				if (beresp == 'R' && (msgLength < 8 || msgLength > 2000))
 				{
@@ -3258,25 +3244,11 @@ keep_going:						/* We will come back to here until there is
 					 */
 					appendPQExpBufferChar(&conn->errorMessage, '\n');
 
-					/*
-					 * If we tried to open the connection in 3.0 protocol,
-					 * fall back to 2.0 protocol.
-					 */
-					if (PG_PROTOCOL_MAJOR(conn->pversion) >= 3)
-					{
-						conn->pversion = PG_PROTOCOL(2, 0);
-						need_new_connection = true;
-						goto keep_going;
-					}
-
 					goto error_return;
 				}
 
 				/*
 				 * Can't process if message body isn't all here yet.
-				 *
-				 * (In protocol 2.0 case, we are assuming messages carry at
-				 * least 4 bytes of data.)
 				 */
 				msgLength -= 4;
 				avail = conn->inEnd - conn->inCursor;
@@ -3297,21 +3269,10 @@ keep_going:						/* We will come back to here until there is
 				/* Handle errors. */
 				if (beresp == 'E')
 				{
-					if (PG_PROTOCOL_MAJOR(conn->pversion) >= 3)
+					if (pqGetErrorNotice3(conn, true))
 					{
-						if (pqGetErrorNotice3(conn, true))
-						{
-							/* We'll come back when there is more data */
-							return PGRES_POLLING_READING;
-						}
-					}
-					else
-					{
-						if (pqGets_append(&conn->errorMessage, conn))
-						{
-							/* We'll come back when there is more data */
-							return PGRES_POLLING_READING;
-						}
+						/* We'll come back when there is more data */
+						return PGRES_POLLING_READING;
 					}
 					/* OK, we read the message; mark data consumed */
 					conn->inStart = conn->inCursor;
@@ -3395,33 +3356,6 @@ keep_going:						/* We will come back to here until there is
 				}
 				msgLength -= 4;
 
-				/*
-				 * Ensure the password salt is in the input buffer, if it's an
-				 * MD5 request.  All the other authentication methods that
-				 * contain extra data in the authentication request are only
-				 * supported in protocol version 3, in which case we already
-				 * read the whole message above.
-				 */
-				if (areq == AUTH_REQ_MD5 && PG_PROTOCOL_MAJOR(conn->pversion) < 3)
-				{
-					msgLength += 4;
-
-					avail = conn->inEnd - conn->inCursor;
-					if (avail < 4)
-					{
-						/*
-						 * Before returning, try to enlarge the input buffer
-						 * if needed to hold the whole message; see notes in
-						 * pqParseInput3.
-						 */
-						if (pqCheckInBufferSpace(conn->inCursor + (size_t) 4,
-												 conn))
-							goto error_return;
-						/* We'll come back when there is more data */
-						return PGRES_POLLING_READING;
-					}
-				}
-
 				/*
 				 * Process the rest of the authentication request message, and
 				 * respond to it if necessary.
@@ -3529,15 +3463,6 @@ keep_going:						/* We will come back to here until there is
 					goto error_return;
 				}
 
-				/* Fire up post-connection housekeeping if needed */
-				if (PG_PROTOCOL_MAJOR(conn->pversion) < 3)
-				{
-					conn->status = CONNECTION_SETENV;
-					conn->setenv_state = SETENV_STATE_CLIENT_ENCODING_SEND;
-					conn->next_eo = EnvironmentOptions;
-					return PGRES_POLLING_WRITING;
-				}
-
 				/* Almost there now ... */
 				conn->status = CONNECTION_CHECK_TARGET;
 				goto keep_going;
@@ -3578,39 +3503,6 @@ keep_going:						/* We will come back to here until there is
 				return PGRES_POLLING_OK;
 			}
 
-		case CONNECTION_SETENV:
-			{
-				/*
-				 * Do post-connection housekeeping (only needed in protocol
-				 * 2.0).
-				 *
-				 * We pretend that the connection is OK for the duration of
-				 * these queries.
-				 */
-				conn->status = CONNECTION_OK;
-
-				switch (pqSetenvPoll(conn))
-				{
-					case PGRES_POLLING_OK:	/* Success */
-						break;
-
-					case PGRES_POLLING_READING: /* Still going */
-						conn->status = CONNECTION_SETENV;
-						return PGRES_POLLING_READING;
-
-					case PGRES_POLLING_WRITING: /* Still going */
-						conn->status = CONNECTION_SETENV;
-						return PGRES_POLLING_WRITING;
-
-					default:
-						goto error_return;
-				}
-
-				/* Almost there now ... */
-				conn->status = CONNECTION_CHECK_TARGET;
-				goto keep_going;
-			}
-
 		case CONNECTION_CONSUME:
 			{
 				conn->status = CONNECTION_OK;
@@ -3852,7 +3744,6 @@ makeEmptyPGconn(void)
 	conn->xactStatus = PQTRANS_IDLE;
 	conn->options_valid = false;
 	conn->nonblocking = false;
-	conn->setenv_state = SETENV_STATE_IDLE;
 	conn->client_encoding = PG_SQL_ASCII;
 	conn->std_strings = false;	/* unless server says differently */
 	conn->verbosity = PQERRORS_DEFAULT;
@@ -4065,7 +3956,7 @@ sendTerminateConn(PGconn *conn)
 		 * Try to send "close connection" message to backend. Ignore any
 		 * error.
 		 */
-		pqPutMsgStart('X', false, conn);
+		pqPutMsgStart('X', conn);
 		pqPutMsgEnd(conn);
 		(void) pqFlush(conn);
 	}
@@ -4458,16 +4349,13 @@ PQrequestCancel(PGconn *conn)
  *
  * RETURNS: STATUS_ERROR if the write fails, STATUS_OK otherwise.
  * SIDE_EFFECTS: may block.
- *
- * Note: all messages sent with this routine have a length word, whether
- * it's protocol 2.0 or 3.0.
  */
 int
 pqPacketSend(PGconn *conn, char pack_type,
 			 const void *buf, size_t buf_len)
 {
 	/* Start the message. */
-	if (pqPutMsgStart(pack_type, true, conn))
+	if (pqPutMsgStart(pack_type, conn))
 		return STATUS_ERROR;
 
 	/* Send the message body. */
@@ -6723,13 +6611,9 @@ PQsetClientEncoding(PGconn *conn, const char *encoding)
 	else
 	{
 		/*
-		 * In protocol 2 we have to assume the setting will stick, and adjust
-		 * our state immediately.  In protocol 3 and up we can rely on the
-		 * backend to report the parameter value, and we'll change state at
-		 * that time.
+		 * We rely on the backend to report the parameter value, and we'll
+		 * change state at that time.
 		 */
-		if (PG_PROTOCOL_MAJOR(conn->pversion) < 3)
-			pqSaveParameterStatus(conn, "client_encoding", encoding);
 		status = 0;				/* everything is ok */
 	}
 	PQclear(res);
diff --git a/src/interfaces/libpq/fe-exec.c b/src/interfaces/libpq/fe-exec.c
index e7307533876..7c7d2017f97 100644
--- a/src/interfaces/libpq/fe-exec.c
+++ b/src/interfaces/libpq/fe-exec.c
@@ -1210,7 +1210,7 @@ PQsendQueryInternal(PGconn *conn, const char *query, bool newQuery)
 	}
 
 	/* construct the outgoing Query message */
-	if (pqPutMsgStart('Q', false, conn) < 0 ||
+	if (pqPutMsgStart('Q', conn) < 0 ||
 		pqPuts(query, conn) < 0 ||
 		pqPutMsgEnd(conn) < 0)
 	{
@@ -1244,7 +1244,7 @@ PQsendQueryInternal(PGconn *conn, const char *query, bool newQuery)
 
 /*
  * PQsendQueryParams
- *		Like PQsendQuery, but use protocol 3.0 so we can pass parameters
+ *		Like PQsendQuery, but use extended query protocol so we can pass parameters
  */
 int
 PQsendQueryParams(PGconn *conn,
@@ -1319,16 +1319,8 @@ PQsendPrepare(PGconn *conn,
 		return 0;
 	}
 
-	/* This isn't gonna work on a 2.0 server */
-	if (PG_PROTOCOL_MAJOR(conn->pversion) < 3)
-	{
-		appendPQExpBufferStr(&conn->errorMessage,
-							 libpq_gettext("function requires at least protocol version 3.0\n"));
-		return 0;
-	}
-
 	/* construct the Parse message */
-	if (pqPutMsgStart('P', false, conn) < 0 ||
+	if (pqPutMsgStart('P', conn) < 0 ||
 		pqPuts(stmtName, conn) < 0 ||
 		pqPuts(query, conn) < 0)
 		goto sendFailed;
@@ -1354,7 +1346,7 @@ PQsendPrepare(PGconn *conn,
 		goto sendFailed;
 
 	/* construct the Sync message */
-	if (pqPutMsgStart('S', false, conn) < 0 ||
+	if (pqPutMsgStart('S', conn) < 0 ||
 		pqPutMsgEnd(conn) < 0)
 		goto sendFailed;
 
@@ -1386,7 +1378,7 @@ sendFailed:
 /*
  * PQsendQueryPrepared
  *		Like PQsendQuery, but execute a previously prepared statement,
- *		using protocol 3.0 so we can pass parameters
+ *		using extended query protocol so we can pass parameters
  */
 int
 PQsendQueryPrepared(PGconn *conn,
@@ -1467,7 +1459,7 @@ PQsendQueryStart(PGconn *conn, bool newQuery)
 
 /*
  * PQsendQueryGuts
- *		Common code for protocol-3.0 query sending
+ *		Common code for sending a query with extended query protocol
  *		PQsendQueryStart should be done already
  *
  * command may be NULL to indicate we use an already-prepared statement
@@ -1485,14 +1477,6 @@ PQsendQueryGuts(PGconn *conn,
 {
 	int			i;
 
-	/* This isn't gonna work on a 2.0 server */
-	if (PG_PROTOCOL_MAJOR(conn->pversion) < 3)
-	{
-		appendPQExpBufferStr(&conn->errorMessage,
-							 libpq_gettext("function requires at least protocol version 3.0\n"));
-		return 0;
-	}
-
 	/*
 	 * We will send Parse (if needed), Bind, Describe Portal, Execute, Sync,
 	 * using specified statement name and the unnamed portal.
@@ -1501,7 +1485,7 @@ PQsendQueryGuts(PGconn *conn,
 	if (command)
 	{
 		/* construct the Parse message */
-		if (pqPutMsgStart('P', false, conn) < 0 ||
+		if (pqPutMsgStart('P', conn) < 0 ||
 			pqPuts(stmtName, conn) < 0 ||
 			pqPuts(command, conn) < 0)
 			goto sendFailed;
@@ -1525,7 +1509,7 @@ PQsendQueryGuts(PGconn *conn,
 	}
 
 	/* Construct the Bind message */
-	if (pqPutMsgStart('B', false, conn) < 0 ||
+	if (pqPutMsgStart('B', conn) < 0 ||
 		pqPuts("", conn) < 0 ||
 		pqPuts(stmtName, conn) < 0)
 		goto sendFailed;
@@ -1592,21 +1576,21 @@ PQsendQueryGuts(PGconn *conn,
 		goto sendFailed;
 
 	/* construct the Describe Portal message */
-	if (pqPutMsgStart('D', false, conn) < 0 ||
+	if (pqPutMsgStart('D', conn) < 0 ||
 		pqPutc('P', conn) < 0 ||
 		pqPuts("", conn) < 0 ||
 		pqPutMsgEnd(conn) < 0)
 		goto sendFailed;
 
 	/* construct the Execute message */
-	if (pqPutMsgStart('E', false, conn) < 0 ||
+	if (pqPutMsgStart('E', conn) < 0 ||
 		pqPuts("", conn) < 0 ||
 		pqPutInt(0, 4, conn) < 0 ||
 		pqPutMsgEnd(conn) < 0)
 		goto sendFailed;
 
 	/* construct the Sync message */
-	if (pqPutMsgStart('S', false, conn) < 0 ||
+	if (pqPutMsgStart('S', conn) < 0 ||
 		pqPutMsgEnd(conn) < 0)
 		goto sendFailed;
 
@@ -1707,10 +1691,7 @@ PQconsumeInput(PGconn *conn)
 static void
 parseInput(PGconn *conn)
 {
-	if (PG_PROTOCOL_MAJOR(conn->pversion) >= 3)
-		pqParseInput3(conn);
-	else
-		pqParseInput2(conn);
+	pqParseInput3(conn);
 }
 
 /*
@@ -1915,7 +1896,7 @@ PQexec(PGconn *conn, const char *query)
 
 /*
  * PQexecParams
- *		Like PQexec, but use protocol 3.0 so we can pass parameters
+ *		Like PQexec, but use extended query protocol so we can pass parameters
  */
 PGresult *
 PQexecParams(PGconn *conn,
@@ -1938,7 +1919,7 @@ PQexecParams(PGconn *conn,
 
 /*
  * PQprepare
- *	  Creates a prepared statement by issuing a v3.0 parse message.
+ *	  Creates a prepared statement by issuing a Parse message.
  *
  * If the query was not even sent, return NULL; conn->errorMessage is set to
  * a relevant message.
@@ -1962,7 +1943,7 @@ PQprepare(PGconn *conn,
 /*
  * PQexecPrepared
  *		Like PQexec, but execute a previously prepared statement,
- *		using protocol 3.0 so we can pass parameters
+ *		using extended query protocol so we can pass parameters
  */
 PGresult *
 PQexecPrepared(PGconn *conn,
@@ -2009,41 +1990,20 @@ PQexecStart(PGconn *conn)
 		PQclear(result);		/* only need its status */
 		if (resultStatus == PGRES_COPY_IN)
 		{
-			if (PG_PROTOCOL_MAJOR(conn->pversion) >= 3)
-			{
-				/* In protocol 3, we can get out of a COPY IN state */
-				if (PQputCopyEnd(conn,
-								 libpq_gettext("COPY terminated by new PQexec")) < 0)
-					return false;
-				/* keep waiting to swallow the copy's failure message */
-			}
-			else
-			{
-				/* In older protocols we have to punt */
-				appendPQExpBufferStr(&conn->errorMessage,
-									 libpq_gettext("COPY IN state must be terminated first\n"));
+			/* get out of a COPY IN state */
+			if (PQputCopyEnd(conn,
+							 libpq_gettext("COPY terminated by new PQexec")) < 0)
 				return false;
-			}
+			/* keep waiting to swallow the copy's failure message */
 		}
 		else if (resultStatus == PGRES_COPY_OUT)
 		{
-			if (PG_PROTOCOL_MAJOR(conn->pversion) >= 3)
-			{
-				/*
-				 * In protocol 3, we can get out of a COPY OUT state: we just
-				 * switch back to BUSY and allow the remaining COPY data to be
-				 * dropped on the floor.
-				 */
-				conn->asyncStatus = PGASYNC_BUSY;
-				/* keep waiting to swallow the copy's completion message */
-			}
-			else
-			{
-				/* In older protocols we have to punt */
-				appendPQExpBufferStr(&conn->errorMessage,
-									 libpq_gettext("COPY OUT state must be terminated first\n"));
-				return false;
-			}
+			/*
+			 * Get out of a COPY OUT state: we just switch back to BUSY and
+			 * allow the remaining COPY data to be dropped on the floor.
+			 */
+			conn->asyncStatus = PGASYNC_BUSY;
+			/* keep waiting to swallow the copy's completion message */
 		}
 		else if (resultStatus == PGRES_COPY_BOTH)
 		{
@@ -2184,23 +2144,15 @@ PQsendDescribe(PGconn *conn, char desc_type, const char *desc_target)
 	if (!PQsendQueryStart(conn, true))
 		return 0;
 
-	/* This isn't gonna work on a 2.0 server */
-	if (PG_PROTOCOL_MAJOR(conn->pversion) < 3)
-	{
-		appendPQExpBufferStr(&conn->errorMessage,
-							 libpq_gettext("function requires at least protocol version 3.0\n"));
-		return 0;
-	}
-
 	/* construct the Describe message */
-	if (pqPutMsgStart('D', false, conn) < 0 ||
+	if (pqPutMsgStart('D', conn) < 0 ||
 		pqPutc(desc_type, conn) < 0 ||
 		pqPuts(desc_target, conn) < 0 ||
 		pqPutMsgEnd(conn) < 0)
 		goto sendFailed;
 
 	/* construct the Sync message */
-	if (pqPutMsgStart('S', false, conn) < 0 ||
+	if (pqPutMsgStart('S', conn) < 0 ||
 		pqPutMsgEnd(conn) < 0)
 		goto sendFailed;
 
@@ -2300,8 +2252,7 @@ PQputCopyData(PGconn *conn, const char *buffer, int nbytes)
 		 * Try to flush any previously sent data in preference to growing the
 		 * output buffer.  If we can't enlarge the buffer enough to hold the
 		 * data, return 0 in the nonblock case, else hard error. (For
-		 * simplicity, always assume 5 bytes of overhead even in protocol 2.0
-		 * case.)
+		 * simplicity, always assume 5 bytes of overhead.)
 		 */
 		if ((conn->outBufSize - conn->outCount - 5) < nbytes)
 		{
@@ -2312,20 +2263,10 @@ PQputCopyData(PGconn *conn, const char *buffer, int nbytes)
 				return pqIsnonblocking(conn) ? 0 : -1;
 		}
 		/* Send the data (too simple to delegate to fe-protocol files) */
-		if (PG_PROTOCOL_MAJOR(conn->pversion) >= 3)
-		{
-			if (pqPutMsgStart('d', false, conn) < 0 ||
-				pqPutnchar(buffer, nbytes, conn) < 0 ||
-				pqPutMsgEnd(conn) < 0)
-				return -1;
-		}
-		else
-		{
-			if (pqPutMsgStart(0, false, conn) < 0 ||
-				pqPutnchar(buffer, nbytes, conn) < 0 ||
-				pqPutMsgEnd(conn) < 0)
-				return -1;
-		}
+		if (pqPutMsgStart('d', conn) < 0 ||
+			pqPutnchar(buffer, nbytes, conn) < 0 ||
+			pqPutMsgEnd(conn) < 0)
+			return -1;
 	}
 	return 1;
 }
@@ -2355,52 +2296,31 @@ PQputCopyEnd(PGconn *conn, const char *errormsg)
 	 * Send the COPY END indicator.  This is simple enough that we don't
 	 * bother delegating it to the fe-protocol files.
 	 */
-	if (PG_PROTOCOL_MAJOR(conn->pversion) >= 3)
+	if (errormsg)
 	{
-		if (errormsg)
-		{
-			/* Send COPY FAIL */
-			if (pqPutMsgStart('f', false, conn) < 0 ||
-				pqPuts(errormsg, conn) < 0 ||
-				pqPutMsgEnd(conn) < 0)
-				return -1;
-		}
-		else
-		{
-			/* Send COPY DONE */
-			if (pqPutMsgStart('c', false, conn) < 0 ||
-				pqPutMsgEnd(conn) < 0)
-				return -1;
-		}
-
-		/*
-		 * If we sent the COPY command in extended-query mode, we must issue a
-		 * Sync as well.
-		 */
-		if (conn->queryclass != PGQUERY_SIMPLE)
-		{
-			if (pqPutMsgStart('S', false, conn) < 0 ||
-				pqPutMsgEnd(conn) < 0)
-				return -1;
-		}
+		/* Send COPY FAIL */
+		if (pqPutMsgStart('f', conn) < 0 ||
+			pqPuts(errormsg, conn) < 0 ||
+			pqPutMsgEnd(conn) < 0)
+			return -1;
 	}
 	else
 	{
-		if (errormsg)
-		{
-			/* Oops, no way to do this in 2.0 */
-			appendPQExpBufferStr(&conn->errorMessage,
-								 libpq_gettext("function requires at least protocol version 3.0\n"));
+		/* Send COPY DONE */
+		if (pqPutMsgStart('c', conn) < 0 ||
+			pqPutMsgEnd(conn) < 0)
+			return -1;
+	}
+
+	/*
+	 * If we sent the COPY command in extended-query mode, we must issue a
+	 * Sync as well.
+	 */
+	if (conn->queryclass != PGQUERY_SIMPLE)
+	{
+		if (pqPutMsgStart('S', conn) < 0 ||
+			pqPutMsgEnd(conn) < 0)
 			return -1;
-		}
-		else
-		{
-			/* Send old-style end-of-data marker */
-			if (pqPutMsgStart(0, false, conn) < 0 ||
-				pqPutnchar("\\.\n", 3, conn) < 0 ||
-				pqPutMsgEnd(conn) < 0)
-				return -1;
-		}
 	}
 
 	/* Return to active duty */
@@ -2439,10 +2359,7 @@ PQgetCopyData(PGconn *conn, char **buffer, int async)
 							 libpq_gettext("no COPY in progress\n"));
 		return -2;
 	}
-	if (PG_PROTOCOL_MAJOR(conn->pversion) >= 3)
-		return pqGetCopyData3(conn, buffer, async);
-	else
-		return pqGetCopyData2(conn, buffer, async);
+	return pqGetCopyData3(conn, buffer, async);
 }
 
 /*
@@ -2481,10 +2398,7 @@ PQgetline(PGconn *conn, char *s, int maxlen)
 	if (!conn)
 		return EOF;
 
-	if (PG_PROTOCOL_MAJOR(conn->pversion) >= 3)
-		return pqGetline3(conn, s, maxlen);
-	else
-		return pqGetline2(conn, s, maxlen);
+	return pqGetline3(conn, s, maxlen);
 }
 
 /*
@@ -2524,10 +2438,7 @@ PQgetlineAsync(PGconn *conn, char *buffer, int bufsize)
 	if (!conn)
 		return -1;
 
-	if (PG_PROTOCOL_MAJOR(conn->pversion) >= 3)
-		return pqGetlineAsync3(conn, buffer, bufsize);
-	else
-		return pqGetlineAsync2(conn, buffer, bufsize);
+	return pqGetlineAsync3(conn, buffer, bufsize);
 }
 
 /*
@@ -2562,10 +2473,8 @@ PQputnbytes(PGconn *conn, const char *buffer, int nbytes)
  *		After completing the data transfer portion of a copy in/out,
  *		the application must call this routine to finish the command protocol.
  *
- * When using protocol 3.0 this is deprecated; it's cleaner to use PQgetResult
- * to get the transfer status.  Note however that when using 2.0 protocol,
- * recovering from a copy failure often requires a PQreset.  PQendcopy will
- * take care of that, PQgetResult won't.
+ * This is deprecated; it's cleaner to use PQgetResult to get the transfer
+ * status.
  *
  * RETURNS:
  *		0 on success
@@ -2577,10 +2486,7 @@ PQendcopy(PGconn *conn)
 	if (!conn)
 		return 0;
 
-	if (PG_PROTOCOL_MAJOR(conn->pversion) >= 3)
-		return pqEndcopy3(conn);
-	else
-		return pqEndcopy2(conn);
+	return pqEndcopy3(conn);
 }
 
 
@@ -2632,16 +2538,10 @@ PQfn(PGconn *conn,
 		return NULL;
 	}
 
-	if (PG_PROTOCOL_MAJOR(conn->pversion) >= 3)
-		return pqFunctionCall3(conn, fnid,
-							   result_buf, result_len,
-							   result_is_int,
-							   args, nargs);
-	else
-		return pqFunctionCall2(conn, fnid,
-							   result_buf, result_len,
-							   result_is_int,
-							   args, nargs);
+	return pqFunctionCall3(conn, fnid,
+						   result_buf, result_len,
+						   result_is_int,
+						   args, nargs);
 }
 
 
@@ -2690,13 +2590,6 @@ PQresultVerboseErrorMessage(const PGresult *res,
 
 	initPQExpBuffer(&workBuf);
 
-	/*
-	 * Currently, we pass this off to fe-protocol3.c in all cases; it will
-	 * behave reasonably sanely with an error reported by fe-protocol2.c as
-	 * well.  If necessary, we could record the protocol version in PGresults
-	 * so as to be able to invoke a version-specific message formatter, but
-	 * for now there's no need.
-	 */
 	pqBuildErrorMessage3(&workBuf, res, verbosity, show_context);
 
 	/* If insufficient memory to format the message, fail cleanly */
diff --git a/src/interfaces/libpq/fe-misc.c b/src/interfaces/libpq/fe-misc.c
index 2bfb6acd895..ce2d24b91fc 100644
--- a/src/interfaces/libpq/fe-misc.c
+++ b/src/interfaces/libpq/fe-misc.c
@@ -484,9 +484,6 @@ pqCheckInBufferSpace(size_t bytes_needed, PGconn *conn)
  * msg_type is the message type byte, or 0 for a message without type byte
  * (only startup messages have no type byte)
  *
- * force_len forces the message to have a length word; otherwise, we add
- * a length word if protocol 3.
- *
  * Returns 0 on success, EOF on error
  *
  * The idea here is that we construct the message in conn->outBuffer,
@@ -497,12 +494,11 @@ pqCheckInBufferSpace(size_t bytes_needed, PGconn *conn)
  *
  * The state variable conn->outMsgStart points to the incomplete message's
  * length word: it is either outCount or outCount+1 depending on whether
- * there is a type byte.  If we are sending a message without length word
- * (pre protocol 3.0 only), then outMsgStart is -1.  The state variable
- * conn->outMsgEnd is the end of the data collected so far.
+ * there is a type byte.  The state variable conn->outMsgEnd is the end of
+ * the data collected so far.
  */
 int
-pqPutMsgStart(char msg_type, bool force_len, PGconn *conn)
+pqPutMsgStart(char msg_type, PGconn *conn)
 {
 	int			lenPos;
 	int			endPos;
@@ -514,14 +510,9 @@ pqPutMsgStart(char msg_type, bool force_len, PGconn *conn)
 		endPos = conn->outCount;
 
 	/* do we want a length word? */
-	if (force_len || PG_PROTOCOL_MAJOR(conn->pversion) >= 3)
-	{
-		lenPos = endPos;
-		/* allow room for message length */
-		endPos += 4;
-	}
-	else
-		lenPos = -1;
+	lenPos = endPos;
+	/* allow room for message length */
+	endPos += 4;
 
 	/* make sure there is room for message header */
 	if (pqCheckOutBufferSpace(endPos, conn))
diff --git a/src/interfaces/libpq/fe-protocol2.c b/src/interfaces/libpq/fe-protocol2.c
deleted file mode 100644
index 6efa53d8b71..00000000000
--- a/src/interfaces/libpq/fe-protocol2.c
+++ /dev/null
@@ -1,1610 +0,0 @@
-/*-------------------------------------------------------------------------
- *
- * fe-protocol2.c
- *	  functions that are specific to frontend/backend protocol version 2
- *
- * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
- * Portions Copyright (c) 1994, Regents of the University of California
- *
- *
- * IDENTIFICATION
- *	  src/interfaces/libpq/fe-protocol2.c
- *
- *-------------------------------------------------------------------------
- */
-#include "postgres_fe.h"
-
-#include <ctype.h>
-#include <fcntl.h>
-
-#ifdef WIN32
-#include "win32.h"
-#else
-#include <unistd.h>
-#ifdef HAVE_NETINET_TCP_H
-#include <netinet/tcp.h>
-#endif
-#endif
-
-#include "libpq-fe.h"
-#include "libpq-int.h"
-#include "port/pg_bswap.h"
-
-static int	getRowDescriptions(PGconn *conn);
-static int	getAnotherTuple(PGconn *conn, bool binary);
-static int	pqGetErrorNotice2(PGconn *conn, bool isError);
-static void checkXactStatus(PGconn *conn, const char *cmdTag);
-static int	getNotify(PGconn *conn);
-
-
-/*
- *		pqSetenvPoll
- *
- * Polls the process of passing the values of a standard set of environment
- * variables to the backend.
- */
-PostgresPollingStatusType
-pqSetenvPoll(PGconn *conn)
-{
-	PGresult   *res;
-
-	if (conn == NULL || conn->status == CONNECTION_BAD)
-		return PGRES_POLLING_FAILED;
-
-	/* Check whether there are any data for us */
-	switch (conn->setenv_state)
-	{
-			/* These are reading states */
-		case SETENV_STATE_CLIENT_ENCODING_WAIT:
-		case SETENV_STATE_OPTION_WAIT:
-		case SETENV_STATE_QUERY1_WAIT:
-		case SETENV_STATE_QUERY2_WAIT:
-			{
-				/* Load waiting data */
-				int			n = pqReadData(conn);
-
-				if (n < 0)
-					goto error_return;
-				if (n == 0)
-					return PGRES_POLLING_READING;
-
-				break;
-			}
-
-			/* These are writing states, so we just proceed. */
-		case SETENV_STATE_CLIENT_ENCODING_SEND:
-		case SETENV_STATE_OPTION_SEND:
-		case SETENV_STATE_QUERY1_SEND:
-		case SETENV_STATE_QUERY2_SEND:
-			break;
-
-			/* Should we raise an error if called when not active? */
-		case SETENV_STATE_IDLE:
-			return PGRES_POLLING_OK;
-
-		default:
-			appendPQExpBuffer(&conn->errorMessage,
-							  libpq_gettext("invalid setenv state %c, probably indicative of memory corruption\n"),
-							  conn->setenv_state);
-			goto error_return;
-	}
-
-	/* We will loop here until there is nothing left to do in this call. */
-	for (;;)
-	{
-		switch (conn->setenv_state)
-		{
-				/*
-				 * The _CLIENT_ENCODING_SEND code is slightly different from
-				 * _OPTION_SEND below (e.g., no getenv() call), which is why a
-				 * different state is used.
-				 */
-			case SETENV_STATE_CLIENT_ENCODING_SEND:
-				{
-					char		setQuery[100];	/* note length limit in
-												 * sprintf below */
-					const char *val = conn->client_encoding_initial;
-
-					if (val)
-					{
-						if (pg_strcasecmp(val, "default") == 0)
-							sprintf(setQuery, "SET client_encoding = DEFAULT");
-						else
-							sprintf(setQuery, "SET client_encoding = '%.60s'",
-									val);
-#ifdef CONNECTDEBUG
-						fprintf(stderr,
-								"Sending client_encoding with %s\n",
-								setQuery);
-#endif
-						if (!PQsendQuery(conn, setQuery))
-							goto error_return;
-
-						conn->setenv_state = SETENV_STATE_CLIENT_ENCODING_WAIT;
-					}
-					else
-						conn->setenv_state = SETENV_STATE_OPTION_SEND;
-					break;
-				}
-
-			case SETENV_STATE_OPTION_SEND:
-				{
-					/*
-					 * Send SET commands for stuff directed by Environment
-					 * Options.  Note: we assume that SET commands won't start
-					 * transaction blocks, even in a 7.3 server with
-					 * autocommit off.
-					 */
-					char		setQuery[100];	/* note length limit in
-												 * sprintf below */
-
-					if (conn->next_eo->envName)
-					{
-						const char *val;
-
-						if ((val = getenv(conn->next_eo->envName)))
-						{
-							if (pg_strcasecmp(val, "default") == 0)
-								sprintf(setQuery, "SET %s = DEFAULT",
-										conn->next_eo->pgName);
-							else
-								sprintf(setQuery, "SET %s = '%.60s'",
-										conn->next_eo->pgName, val);
-#ifdef CONNECTDEBUG
-							fprintf(stderr,
-									"Use environment variable %s to send %s\n",
-									conn->next_eo->envName, setQuery);
-#endif
-							if (!PQsendQuery(conn, setQuery))
-								goto error_return;
-
-							conn->setenv_state = SETENV_STATE_OPTION_WAIT;
-						}
-						else
-							conn->next_eo++;
-					}
-					else
-					{
-						/* No more options to send, so move on to querying */
-						conn->setenv_state = SETENV_STATE_QUERY1_SEND;
-					}
-					break;
-				}
-
-			case SETENV_STATE_CLIENT_ENCODING_WAIT:
-				{
-					if (PQisBusy(conn))
-						return PGRES_POLLING_READING;
-
-					res = PQgetResult(conn);
-
-					if (res)
-					{
-						if (PQresultStatus(res) != PGRES_COMMAND_OK)
-						{
-							PQclear(res);
-							goto error_return;
-						}
-						PQclear(res);
-						/* Keep reading until PQgetResult returns NULL */
-					}
-					else
-					{
-						/* Query finished, so send the next option */
-						conn->setenv_state = SETENV_STATE_OPTION_SEND;
-					}
-					break;
-				}
-
-			case SETENV_STATE_OPTION_WAIT:
-				{
-					if (PQisBusy(conn))
-						return PGRES_POLLING_READING;
-
-					res = PQgetResult(conn);
-
-					if (res)
-					{
-						if (PQresultStatus(res) != PGRES_COMMAND_OK)
-						{
-							PQclear(res);
-							goto error_return;
-						}
-						PQclear(res);
-						/* Keep reading until PQgetResult returns NULL */
-					}
-					else
-					{
-						/* Query finished, so send the next option */
-						conn->next_eo++;
-						conn->setenv_state = SETENV_STATE_OPTION_SEND;
-					}
-					break;
-				}
-
-			case SETENV_STATE_QUERY1_SEND:
-				{
-					/*
-					 * Issue query to get information we need.  Here we must
-					 * use begin/commit in case autocommit is off by default
-					 * in a 7.3 server.
-					 *
-					 * Note: version() exists in all protocol-2.0-supporting
-					 * backends.  In 7.3 it would be safer to write
-					 * pg_catalog.version(), but we can't do that without
-					 * causing problems on older versions.
-					 */
-					if (!PQsendQuery(conn, "begin; select version(); end"))
-						goto error_return;
-
-					conn->setenv_state = SETENV_STATE_QUERY1_WAIT;
-					return PGRES_POLLING_READING;
-				}
-
-			case SETENV_STATE_QUERY1_WAIT:
-				{
-					if (PQisBusy(conn))
-						return PGRES_POLLING_READING;
-
-					res = PQgetResult(conn);
-
-					if (res)
-					{
-						char	   *val;
-
-						if (PQresultStatus(res) == PGRES_COMMAND_OK)
-						{
-							/* ignore begin/commit command results */
-							PQclear(res);
-							continue;
-						}
-
-						if (PQresultStatus(res) != PGRES_TUPLES_OK ||
-							PQntuples(res) != 1)
-						{
-							PQclear(res);
-							goto error_return;
-						}
-
-						/*
-						 * Extract server version and save as if
-						 * ParameterStatus
-						 */
-						val = PQgetvalue(res, 0, 0);
-						if (val && strncmp(val, "PostgreSQL ", 11) == 0)
-						{
-							char	   *ptr;
-
-							/* strip off PostgreSQL part */
-							val += 11;
-
-							/*
-							 * strip off platform part (scribbles on result,
-							 * naughty naughty)
-							 */
-							ptr = strchr(val, ' ');
-							if (ptr)
-								*ptr = '\0';
-
-							pqSaveParameterStatus(conn, "server_version",
-												  val);
-						}
-
-						PQclear(res);
-						/* Keep reading until PQgetResult returns NULL */
-					}
-					else
-					{
-						/* Query finished, move to next */
-						conn->setenv_state = SETENV_STATE_QUERY2_SEND;
-					}
-					break;
-				}
-
-			case SETENV_STATE_QUERY2_SEND:
-				{
-					const char *query;
-
-					/*
-					 * pg_client_encoding does not exist in pre-7.2 servers.
-					 * So we need to be prepared for an error here.  Do *not*
-					 * start a transaction block, except in 7.3 servers where
-					 * we need to prevent autocommit-off from starting a
-					 * transaction anyway.
-					 */
-					if (conn->sversion >= 70300 &&
-						conn->sversion < 70400)
-						query = "begin; select pg_catalog.pg_client_encoding(); end";
-					else
-						query = "select pg_client_encoding()";
-					if (!PQsendQuery(conn, query))
-						goto error_return;
-
-					conn->setenv_state = SETENV_STATE_QUERY2_WAIT;
-					return PGRES_POLLING_READING;
-				}
-
-			case SETENV_STATE_QUERY2_WAIT:
-				{
-					if (PQisBusy(conn))
-						return PGRES_POLLING_READING;
-
-					res = PQgetResult(conn);
-
-					if (res)
-					{
-						const char *val;
-
-						if (PQresultStatus(res) == PGRES_COMMAND_OK)
-						{
-							/* ignore begin/commit command results */
-							PQclear(res);
-							continue;
-						}
-
-						if (PQresultStatus(res) == PGRES_TUPLES_OK &&
-							PQntuples(res) == 1)
-						{
-							/* Extract client encoding and save it */
-							val = PQgetvalue(res, 0, 0);
-							if (val && *val)	/* null should not happen, but */
-								pqSaveParameterStatus(conn, "client_encoding",
-													  val);
-						}
-						else
-						{
-							/*
-							 * Error: presumably function not available, so
-							 * use PGCLIENTENCODING or SQL_ASCII as the
-							 * fallback.
-							 */
-							val = getenv("PGCLIENTENCODING");
-							if (val && *val)
-								pqSaveParameterStatus(conn, "client_encoding",
-													  val);
-							else
-								pqSaveParameterStatus(conn, "client_encoding",
-													  "SQL_ASCII");
-						}
-
-						PQclear(res);
-						/* Keep reading until PQgetResult returns NULL */
-					}
-					else
-					{
-						/* Query finished, so we're done */
-						conn->setenv_state = SETENV_STATE_IDLE;
-						return PGRES_POLLING_OK;
-					}
-					break;
-				}
-
-			default:
-				appendPQExpBuffer(&conn->errorMessage,
-								  libpq_gettext("invalid state %c, "
-												"probably indicative of memory corruption\n"),
-								  conn->setenv_state);
-				goto error_return;
-		}
-	}
-
-	/* Unreachable */
-
-error_return:
-	conn->setenv_state = SETENV_STATE_IDLE;
-	return PGRES_POLLING_FAILED;
-}
-
-
-/*
- * parseInput: if appropriate, parse input data from backend
- * until input is exhausted or a stopping state is reached.
- * Note that this function will NOT attempt to read more data from the backend.
- */
-void
-pqParseInput2(PGconn *conn)
-{
-	char		id;
-
-	/*
-	 * Loop to parse successive complete messages available in the buffer.
-	 */
-	for (;;)
-	{
-		/*
-		 * Quit if in COPY_OUT state: we expect raw data from the server until
-		 * PQendcopy is called.  Don't try to parse it according to the normal
-		 * protocol.  (This is bogus.  The data lines ought to be part of the
-		 * protocol and have identifying leading characters.)
-		 */
-		if (conn->asyncStatus == PGASYNC_COPY_OUT)
-			return;
-
-		/*
-		 * OK to try to read a message type code.
-		 */
-		conn->inCursor = conn->inStart;
-		if (pqGetc(&id, conn))
-			return;
-
-		/*
-		 * NOTIFY and NOTICE messages can happen in any state besides COPY
-		 * OUT; always process them right away.
-		 *
-		 * Most other messages should only be processed while in BUSY state.
-		 * (In particular, in READY state we hold off further parsing until
-		 * the application collects the current PGresult.)
-		 *
-		 * However, if the state is IDLE then we got trouble; we need to deal
-		 * with the unexpected message somehow.
-		 */
-		if (id == 'A')
-		{
-			if (getNotify(conn))
-				return;
-		}
-		else if (id == 'N')
-		{
-			if (pqGetErrorNotice2(conn, false))
-				return;
-		}
-		else if (conn->asyncStatus != PGASYNC_BUSY)
-		{
-			/* If not IDLE state, just wait ... */
-			if (conn->asyncStatus != PGASYNC_IDLE)
-				return;
-
-			/*
-			 * Unexpected message in IDLE state; need to recover somehow.
-			 * ERROR messages are displayed using the notice processor;
-			 * anything else is just dropped on the floor after displaying a
-			 * suitable warning notice.  (An ERROR is very possibly the
-			 * backend telling us why it is about to close the connection, so
-			 * we don't want to just discard it...)
-			 */
-			if (id == 'E')
-			{
-				if (pqGetErrorNotice2(conn, false /* treat as notice */ ))
-					return;
-			}
-			else
-			{
-				pqInternalNotice(&conn->noticeHooks,
-								 "message type 0x%02x arrived from server while idle",
-								 id);
-				/* Discard the unexpected message; good idea?? */
-				conn->inStart = conn->inEnd;
-				break;
-			}
-		}
-		else
-		{
-			/*
-			 * In BUSY state, we can process everything.
-			 */
-			switch (id)
-			{
-				case 'C':		/* command complete */
-					if (pqGets(&conn->workBuffer, conn))
-						return;
-					if (conn->result == NULL)
-					{
-						conn->result = PQmakeEmptyPGresult(conn,
-														   PGRES_COMMAND_OK);
-						if (!conn->result)
-						{
-							appendPQExpBufferStr(&conn->errorMessage,
-												 libpq_gettext("out of memory"));
-							pqSaveErrorResult(conn);
-						}
-					}
-					if (conn->result)
-					{
-						strlcpy(conn->result->cmdStatus, conn->workBuffer.data,
-								CMDSTATUS_LEN);
-					}
-					checkXactStatus(conn, conn->workBuffer.data);
-					conn->asyncStatus = PGASYNC_READY;
-					break;
-				case 'E':		/* error return */
-					if (pqGetErrorNotice2(conn, true))
-						return;
-					conn->asyncStatus = PGASYNC_READY;
-					break;
-				case 'Z':		/* backend is ready for new query */
-					conn->asyncStatus = PGASYNC_IDLE;
-					break;
-				case 'I':		/* empty query */
-					/* read and throw away the closing '\0' */
-					if (pqGetc(&id, conn))
-						return;
-					if (id != '\0')
-						pqInternalNotice(&conn->noticeHooks,
-										 "unexpected character %c following empty query response (\"I\" message)",
-										 id);
-					if (conn->result == NULL)
-					{
-						conn->result = PQmakeEmptyPGresult(conn,
-														   PGRES_EMPTY_QUERY);
-						if (!conn->result)
-						{
-							appendPQExpBufferStr(&conn->errorMessage,
-												 libpq_gettext("out of memory"));
-							pqSaveErrorResult(conn);
-						}
-					}
-					conn->asyncStatus = PGASYNC_READY;
-					break;
-				case 'K':		/* secret key data from the backend */
-
-					/*
-					 * This is expected only during backend startup, but it's
-					 * just as easy to handle it as part of the main loop.
-					 * Save the data and continue processing.
-					 */
-					if (pqGetInt(&(conn->be_pid), 4, conn))
-						return;
-					if (pqGetInt(&(conn->be_key), 4, conn))
-						return;
-					break;
-				case 'P':		/* synchronous (normal) portal */
-					if (pqGets(&conn->workBuffer, conn))
-						return;
-					/* We pretty much ignore this message type... */
-					break;
-				case 'T':		/* row descriptions (start of query results) */
-					if (conn->result == NULL)
-					{
-						/* First 'T' in a query sequence */
-						if (getRowDescriptions(conn))
-							return;
-						/* getRowDescriptions() moves inStart itself */
-						continue;
-					}
-					else
-					{
-						/*
-						 * A new 'T' message is treated as the start of
-						 * another PGresult.  (It is not clear that this is
-						 * really possible with the current backend.) We stop
-						 * parsing until the application accepts the current
-						 * result.
-						 */
-						conn->asyncStatus = PGASYNC_READY;
-						return;
-					}
-					break;
-				case 'D':		/* ASCII data tuple */
-					if (conn->result != NULL)
-					{
-						/* Read another tuple of a normal query response */
-						if (getAnotherTuple(conn, false))
-							return;
-						/* getAnotherTuple() moves inStart itself */
-						continue;
-					}
-					else
-					{
-						pqInternalNotice(&conn->noticeHooks,
-										 "server sent data (\"D\" message) without prior row description (\"T\" message)");
-						/* Discard the unexpected message; good idea?? */
-						conn->inStart = conn->inEnd;
-						return;
-					}
-					break;
-				case 'B':		/* Binary data tuple */
-					if (conn->result != NULL)
-					{
-						/* Read another tuple of a normal query response */
-						if (getAnotherTuple(conn, true))
-							return;
-						/* getAnotherTuple() moves inStart itself */
-						continue;
-					}
-					else
-					{
-						pqInternalNotice(&conn->noticeHooks,
-										 "server sent binary data (\"B\" message) without prior row description (\"T\" message)");
-						/* Discard the unexpected message; good idea?? */
-						conn->inStart = conn->inEnd;
-						return;
-					}
-					break;
-				case 'G':		/* Start Copy In */
-					conn->asyncStatus = PGASYNC_COPY_IN;
-					break;
-				case 'H':		/* Start Copy Out */
-					conn->asyncStatus = PGASYNC_COPY_OUT;
-					break;
-
-					/*
-					 * Don't need to process CopyBothResponse here because it
-					 * never arrives from the server during protocol 2.0.
-					 */
-				default:
-					appendPQExpBuffer(&conn->errorMessage,
-									  libpq_gettext("unexpected response from server; first received character was \"%c\"\n"),
-									  id);
-					/* build an error result holding the error message */
-					pqSaveErrorResult(conn);
-					/* Discard the unexpected message; good idea?? */
-					conn->inStart = conn->inEnd;
-					conn->asyncStatus = PGASYNC_READY;
-					return;
-			}					/* switch on protocol character */
-		}
-		/* Successfully consumed this message */
-		conn->inStart = conn->inCursor;
-	}
-}
-
-/*
- * parseInput subroutine to read a 'T' (row descriptions) message.
- * We build a PGresult structure containing the attribute data.
- * Returns: 0 if completed message, EOF if error or not enough data
- * received yet.
- *
- * Note that if we run out of data, we have to suspend and reprocess
- * the message after more data is received.  Otherwise, conn->inStart
- * must get advanced past the processed data.
- */
-static int
-getRowDescriptions(PGconn *conn)
-{
-	PGresult   *result;
-	int			nfields;
-	const char *errmsg;
-	int			i;
-
-	result = PQmakeEmptyPGresult(conn, PGRES_TUPLES_OK);
-	if (!result)
-	{
-		errmsg = NULL;			/* means "out of memory", see below */
-		goto advance_and_error;
-	}
-
-	/* parseInput already read the 'T' label. */
-	/* the next two bytes are the number of fields	*/
-	if (pqGetInt(&(result->numAttributes), 2, conn))
-		goto EOFexit;
-	nfields = result->numAttributes;
-
-	/* allocate space for the attribute descriptors */
-	if (nfields > 0)
-	{
-		result->attDescs = (PGresAttDesc *)
-			pqResultAlloc(result, nfields * sizeof(PGresAttDesc), true);
-		if (!result->attDescs)
-		{
-			errmsg = NULL;		/* means "out of memory", see below */
-			goto advance_and_error;
-		}
-		MemSet(result->attDescs, 0, nfields * sizeof(PGresAttDesc));
-	}
-
-	/* get type info */
-	for (i = 0; i < nfields; i++)
-	{
-		int			typid;
-		int			typlen;
-		int			atttypmod;
-
-		if (pqGets(&conn->workBuffer, conn) ||
-			pqGetInt(&typid, 4, conn) ||
-			pqGetInt(&typlen, 2, conn) ||
-			pqGetInt(&atttypmod, 4, conn))
-			goto EOFexit;
-
-		/*
-		 * Since pqGetInt treats 2-byte integers as unsigned, we need to
-		 * coerce the result to signed form.
-		 */
-		typlen = (int) ((int16) typlen);
-
-		result->attDescs[i].name = pqResultStrdup(result,
-												  conn->workBuffer.data);
-		if (!result->attDescs[i].name)
-		{
-			errmsg = NULL;		/* means "out of memory", see below */
-			goto advance_and_error;
-		}
-		result->attDescs[i].tableid = 0;
-		result->attDescs[i].columnid = 0;
-		result->attDescs[i].format = 0;
-		result->attDescs[i].typid = typid;
-		result->attDescs[i].typlen = typlen;
-		result->attDescs[i].atttypmod = atttypmod;
-	}
-
-	/* Success! */
-	conn->result = result;
-
-	/* Advance inStart to show that the "T" message has been processed. */
-	conn->inStart = conn->inCursor;
-
-	/*
-	 * We could perform additional setup for the new result set here, but for
-	 * now there's nothing else to do.
-	 */
-
-	/* And we're done. */
-	return 0;
-
-advance_and_error:
-
-	/*
-	 * Discard the failed message.  Unfortunately we don't know for sure where
-	 * the end is, so just throw away everything in the input buffer. This is
-	 * not very desirable but it's the best we can do in protocol v2.
-	 */
-	conn->inStart = conn->inEnd;
-
-	/*
-	 * Replace partially constructed result with an error result. First
-	 * discard the old result to try to win back some memory.
-	 */
-	pqClearAsyncResult(conn);
-
-	/*
-	 * If preceding code didn't provide an error message, assume "out of
-	 * memory" was meant.  The advantage of having this special case is that
-	 * freeing the old result first greatly improves the odds that gettext()
-	 * will succeed in providing a translation.
-	 */
-	if (!errmsg)
-		errmsg = libpq_gettext("out of memory for query result");
-
-	appendPQExpBuffer(&conn->errorMessage, "%s\n", errmsg);
-
-	/*
-	 * XXX: if PQmakeEmptyPGresult() fails, there's probably not much we can
-	 * do to recover...
-	 */
-	conn->result = PQmakeEmptyPGresult(conn, PGRES_FATAL_ERROR);
-	conn->asyncStatus = PGASYNC_READY;
-
-EOFexit:
-	if (result && result != conn->result)
-		PQclear(result);
-	return EOF;
-}
-
-/*
- * parseInput subroutine to read a 'B' or 'D' (row data) message.
- * We fill rowbuf with column pointers and then call the row processor.
- * Returns: 0 if completed message, EOF if error or not enough data
- * received yet.
- *
- * Note that if we run out of data, we have to suspend and reprocess
- * the message after more data is received.  Otherwise, conn->inStart
- * must get advanced past the processed data.
- */
-static int
-getAnotherTuple(PGconn *conn, bool binary)
-{
-	PGresult   *result = conn->result;
-	int			nfields = result->numAttributes;
-	const char *errmsg;
-	PGdataValue *rowbuf;
-
-	/* the backend sends us a bitmap of which attributes are null */
-	char		std_bitmap[64]; /* used unless it doesn't fit */
-	char	   *bitmap = std_bitmap;
-	int			i;
-	size_t		nbytes;			/* the number of bytes in bitmap  */
-	char		bmap;			/* One byte of the bitmap */
-	int			bitmap_index;	/* Its index */
-	int			bitcnt;			/* number of bits examined in current byte */
-	int			vlen;			/* length of the current field value */
-
-	/* Resize row buffer if needed */
-	rowbuf = conn->rowBuf;
-	if (nfields > conn->rowBufLen)
-	{
-		rowbuf = (PGdataValue *) realloc(rowbuf,
-										 nfields * sizeof(PGdataValue));
-		if (!rowbuf)
-		{
-			errmsg = NULL;		/* means "out of memory", see below */
-			goto advance_and_error;
-		}
-		conn->rowBuf = rowbuf;
-		conn->rowBufLen = nfields;
-	}
-
-	/* Save format specifier */
-	result->binary = binary;
-
-	/*
-	 * If it's binary, fix the column format indicators.  We assume the
-	 * backend will consistently send either B or D, not a mix.
-	 */
-	if (binary)
-	{
-		for (i = 0; i < nfields; i++)
-			result->attDescs[i].format = 1;
-	}
-
-	/* Get the null-value bitmap */
-	nbytes = (nfields + BITS_PER_BYTE - 1) / BITS_PER_BYTE;
-	/* malloc() only for unusually large field counts... */
-	if (nbytes > sizeof(std_bitmap))
-	{
-		bitmap = (char *) malloc(nbytes);
-		if (!bitmap)
-		{
-			errmsg = NULL;		/* means "out of memory", see below */
-			goto advance_and_error;
-		}
-	}
-
-	if (pqGetnchar(bitmap, nbytes, conn))
-		goto EOFexit;
-
-	/* Scan the fields */
-	bitmap_index = 0;
-	bmap = bitmap[bitmap_index];
-	bitcnt = 0;
-
-	for (i = 0; i < nfields; i++)
-	{
-		/* get the value length */
-		if (!(bmap & 0200))
-			vlen = NULL_LEN;
-		else if (pqGetInt(&vlen, 4, conn))
-			goto EOFexit;
-		else
-		{
-			if (!binary)
-				vlen = vlen - 4;
-			if (vlen < 0)
-				vlen = 0;
-		}
-		rowbuf[i].len = vlen;
-
-		/*
-		 * rowbuf[i].value always points to the next address in the data
-		 * buffer even if the value is NULL.  This allows row processors to
-		 * estimate data sizes more easily.
-		 */
-		rowbuf[i].value = conn->inBuffer + conn->inCursor;
-
-		/* Skip over the data value */
-		if (vlen > 0)
-		{
-			if (pqSkipnchar(vlen, conn))
-				goto EOFexit;
-		}
-
-		/* advance the bitmap stuff */
-		bitcnt++;
-		if (bitcnt == BITS_PER_BYTE)
-		{
-			bitmap_index++;
-			bmap = bitmap[bitmap_index];
-			bitcnt = 0;
-		}
-		else
-			bmap <<= 1;
-	}
-
-	/* Release bitmap now if we allocated it */
-	if (bitmap != std_bitmap)
-		free(bitmap);
-	bitmap = NULL;
-
-	/* Advance inStart to show that the "D" message has been processed. */
-	conn->inStart = conn->inCursor;
-
-	/* Process the collected row */
-	errmsg = NULL;
-	if (pqRowProcessor(conn, &errmsg))
-		return 0;				/* normal, successful exit */
-
-	goto set_error_result;		/* pqRowProcessor failed, report it */
-
-advance_and_error:
-
-	/*
-	 * Discard the failed message.  Unfortunately we don't know for sure where
-	 * the end is, so just throw away everything in the input buffer. This is
-	 * not very desirable but it's the best we can do in protocol v2.
-	 */
-	conn->inStart = conn->inEnd;
-
-set_error_result:
-
-	/*
-	 * Replace partially constructed result with an error result. First
-	 * discard the old result to try to win back some memory.
-	 */
-	pqClearAsyncResult(conn);
-
-	/*
-	 * If preceding code didn't provide an error message, assume "out of
-	 * memory" was meant.  The advantage of having this special case is that
-	 * freeing the old result first greatly improves the odds that gettext()
-	 * will succeed in providing a translation.
-	 */
-	if (!errmsg)
-		errmsg = libpq_gettext("out of memory for query result");
-
-	appendPQExpBuffer(&conn->errorMessage, "%s\n", errmsg);
-
-	/*
-	 * XXX: if PQmakeEmptyPGresult() fails, there's probably not much we can
-	 * do to recover...
-	 */
-	conn->result = PQmakeEmptyPGresult(conn, PGRES_FATAL_ERROR);
-	conn->asyncStatus = PGASYNC_READY;
-
-EOFexit:
-	if (bitmap != NULL && bitmap != std_bitmap)
-		free(bitmap);
-	return EOF;
-}
-
-
-/*
- * Attempt to read an Error or Notice response message.
- * This is possible in several places, so we break it out as a subroutine.
- * Entry: 'E' or 'N' message type has already been consumed.
- * Exit: returns 0 if successfully consumed message.
- *		 returns EOF if not enough data.
- */
-static int
-pqGetErrorNotice2(PGconn *conn, bool isError)
-{
-	PGresult   *res = NULL;
-	PQExpBufferData workBuf;
-	char	   *startp;
-	char	   *splitp;
-
-	/*
-	 * If this is an error message, pre-emptively clear any incomplete query
-	 * result we may have.  We'd just throw it away below anyway, and
-	 * releasing it before collecting the error might avoid out-of-memory.
-	 */
-	if (isError)
-		pqClearAsyncResult(conn);
-
-	/*
-	 * Since the message might be pretty long, we create a temporary
-	 * PQExpBuffer rather than using conn->workBuffer.  workBuffer is intended
-	 * for stuff that is expected to be short.
-	 */
-	initPQExpBuffer(&workBuf);
-	if (pqGets(&workBuf, conn))
-		goto failure;
-
-	/*
-	 * Make a PGresult to hold the message.  We temporarily lie about the
-	 * result status, so that PQmakeEmptyPGresult doesn't uselessly copy
-	 * conn->errorMessage.
-	 *
-	 * NB: This allocation can fail, if you run out of memory. The rest of the
-	 * function handles that gracefully, and we still try to set the error
-	 * message as the connection's error message.
-	 */
-	res = PQmakeEmptyPGresult(conn, PGRES_EMPTY_QUERY);
-	if (res)
-	{
-		res->resultStatus = isError ? PGRES_FATAL_ERROR : PGRES_NONFATAL_ERROR;
-		res->errMsg = pqResultStrdup(res, workBuf.data);
-	}
-
-	/*
-	 * Break the message into fields.  We can't do very much here, but we can
-	 * split the severity code off, and remove trailing newlines. Also, we use
-	 * the heuristic that the primary message extends only to the first
-	 * newline --- anything after that is detail message.  (In some cases it'd
-	 * be better classed as hint, but we can hardly be expected to guess that
-	 * here.)
-	 */
-	while (workBuf.len > 0 && workBuf.data[workBuf.len - 1] == '\n')
-		workBuf.data[--workBuf.len] = '\0';
-	splitp = strstr(workBuf.data, ":  ");
-	if (splitp)
-	{
-		/* what comes before the colon is severity */
-		*splitp = '\0';
-		pqSaveMessageField(res, PG_DIAG_SEVERITY, workBuf.data);
-		startp = splitp + 3;
-	}
-	else
-	{
-		/* can't find a colon?  oh well... */
-		startp = workBuf.data;
-	}
-	splitp = strchr(startp, '\n');
-	if (splitp)
-	{
-		/* what comes before the newline is primary message */
-		*splitp++ = '\0';
-		pqSaveMessageField(res, PG_DIAG_MESSAGE_PRIMARY, startp);
-		/* the rest is detail; strip any leading whitespace */
-		while (*splitp && isspace((unsigned char) *splitp))
-			splitp++;
-		pqSaveMessageField(res, PG_DIAG_MESSAGE_DETAIL, splitp);
-	}
-	else
-	{
-		/* single-line message, so all primary */
-		pqSaveMessageField(res, PG_DIAG_MESSAGE_PRIMARY, startp);
-	}
-
-	/*
-	 * Either save error as current async result, or just emit the notice.
-	 * Also, if it's an error and we were in a transaction block, assume the
-	 * server has now gone to error-in-transaction state.
-	 */
-	if (isError)
-	{
-		pqClearAsyncResult(conn);	/* redundant, but be safe */
-		conn->result = res;
-		if (res && !PQExpBufferDataBroken(workBuf) && res->errMsg)
-			appendPQExpBufferStr(&conn->errorMessage, res->errMsg);
-		else
-			appendPQExpBufferStr(&conn->errorMessage,
-								 libpq_gettext("out of memory"));
-		if (conn->xactStatus == PQTRANS_INTRANS)
-			conn->xactStatus = PQTRANS_INERROR;
-	}
-	else
-	{
-		if (res)
-		{
-			if (res->noticeHooks.noticeRec != NULL)
-				res->noticeHooks.noticeRec(res->noticeHooks.noticeRecArg, res);
-			PQclear(res);
-		}
-	}
-
-	termPQExpBuffer(&workBuf);
-	return 0;
-
-failure:
-	if (res)
-		PQclear(res);
-	termPQExpBuffer(&workBuf);
-	return EOF;
-}
-
-/*
- * checkXactStatus - attempt to track transaction-block status of server
- *
- * This is called each time we receive a command-complete message.  By
- * watching for messages from BEGIN/COMMIT/ROLLBACK commands, we can do
- * a passable job of tracking the server's xact status.  BUT: this does
- * not work at all on 7.3 servers with AUTOCOMMIT OFF.  (Man, was that
- * feature ever a mistake.)  Caveat user.
- *
- * The tags known here are all those used as far back as 7.0; is it worth
- * adding those from even-older servers?
- */
-static void
-checkXactStatus(PGconn *conn, const char *cmdTag)
-{
-	if (strcmp(cmdTag, "BEGIN") == 0)
-		conn->xactStatus = PQTRANS_INTRANS;
-	else if (strcmp(cmdTag, "COMMIT") == 0)
-		conn->xactStatus = PQTRANS_IDLE;
-	else if (strcmp(cmdTag, "ROLLBACK") == 0)
-		conn->xactStatus = PQTRANS_IDLE;
-	else if (strcmp(cmdTag, "START TRANSACTION") == 0)	/* 7.3 only */
-		conn->xactStatus = PQTRANS_INTRANS;
-
-	/*
-	 * Normally we get into INERROR state by detecting an Error message.
-	 * However, if we see one of these tags then we know for sure the server
-	 * is in abort state ...
-	 */
-	else if (strcmp(cmdTag, "*ABORT STATE*") == 0)	/* pre-7.3 only */
-		conn->xactStatus = PQTRANS_INERROR;
-}
-
-/*
- * Attempt to read a Notify response message.
- * This is possible in several places, so we break it out as a subroutine.
- * Entry: 'A' message type and length have already been consumed.
- * Exit: returns 0 if successfully consumed Notify message.
- *		 returns EOF if not enough data.
- */
-static int
-getNotify(PGconn *conn)
-{
-	int			be_pid;
-	int			nmlen;
-	PGnotify   *newNotify;
-
-	if (pqGetInt(&be_pid, 4, conn))
-		return EOF;
-	if (pqGets(&conn->workBuffer, conn))
-		return EOF;
-
-	/*
-	 * Store the relation name right after the PQnotify structure so it can
-	 * all be freed at once.  We don't use NAMEDATALEN because we don't want
-	 * to tie this interface to a specific server name length.
-	 */
-	nmlen = strlen(conn->workBuffer.data);
-	newNotify = (PGnotify *) malloc(sizeof(PGnotify) + nmlen + 1);
-	if (newNotify)
-	{
-		newNotify->relname = (char *) newNotify + sizeof(PGnotify);
-		strcpy(newNotify->relname, conn->workBuffer.data);
-		/* fake up an empty-string extra field */
-		newNotify->extra = newNotify->relname + nmlen;
-		newNotify->be_pid = be_pid;
-		newNotify->next = NULL;
-		if (conn->notifyTail)
-			conn->notifyTail->next = newNotify;
-		else
-			conn->notifyHead = newNotify;
-		conn->notifyTail = newNotify;
-	}
-
-	return 0;
-}
-
-
-/*
- * PQgetCopyData - read a row of data from the backend during COPY OUT
- *
- * If successful, sets *buffer to point to a malloc'd row of data, and
- * returns row length (always > 0) as result.
- * Returns 0 if no row available yet (only possible if async is true),
- * -1 if end of copy (consult PQgetResult), or -2 if error (consult
- * PQerrorMessage).
- */
-int
-pqGetCopyData2(PGconn *conn, char **buffer, int async)
-{
-	bool		found;
-	int			msgLength;
-
-	for (;;)
-	{
-		/*
-		 * Do we have a complete line of data?
-		 */
-		conn->inCursor = conn->inStart;
-		found = false;
-		while (conn->inCursor < conn->inEnd)
-		{
-			char		c = conn->inBuffer[conn->inCursor++];
-
-			if (c == '\n')
-			{
-				found = true;
-				break;
-			}
-		}
-		if (!found)
-			goto nodata;
-		msgLength = conn->inCursor - conn->inStart;
-
-		/*
-		 * If it's the end-of-data marker, consume it, exit COPY_OUT mode, and
-		 * let caller read status with PQgetResult().
-		 */
-		if (msgLength == 3 &&
-			strncmp(&conn->inBuffer[conn->inStart], "\\.\n", 3) == 0)
-		{
-			conn->inStart = conn->inCursor;
-			conn->asyncStatus = PGASYNC_BUSY;
-			return -1;
-		}
-
-		/*
-		 * Pass the line back to the caller.
-		 */
-		*buffer = (char *) malloc(msgLength + 1);
-		if (*buffer == NULL)
-		{
-			appendPQExpBufferStr(&conn->errorMessage,
-								 libpq_gettext("out of memory\n"));
-			return -2;
-		}
-		memcpy(*buffer, &conn->inBuffer[conn->inStart], msgLength);
-		(*buffer)[msgLength] = '\0';	/* Add terminating null */
-
-		/* Mark message consumed */
-		conn->inStart = conn->inCursor;
-
-		return msgLength;
-
-nodata:
-		/* Don't block if async read requested */
-		if (async)
-			return 0;
-		/* Need to load more data */
-		if (pqWait(true, false, conn) ||
-			pqReadData(conn) < 0)
-			return -2;
-	}
-}
-
-
-/*
- * PQgetline - gets a newline-terminated string from the backend.
- *
- * See fe-exec.c for documentation.
- */
-int
-pqGetline2(PGconn *conn, char *s, int maxlen)
-{
-	int			result = 1;		/* return value if buffer overflows */
-
-	if (conn->sock == PGINVALID_SOCKET ||
-		conn->asyncStatus != PGASYNC_COPY_OUT)
-	{
-		*s = '\0';
-		return EOF;
-	}
-
-	/*
-	 * Since this is a purely synchronous routine, we don't bother to maintain
-	 * conn->inCursor; there is no need to back up.
-	 */
-	while (maxlen > 1)
-	{
-		if (conn->inStart < conn->inEnd)
-		{
-			char		c = conn->inBuffer[conn->inStart++];
-
-			if (c == '\n')
-			{
-				result = 0;		/* success exit */
-				break;
-			}
-			*s++ = c;
-			maxlen--;
-		}
-		else
-		{
-			/* need to load more data */
-			if (pqWait(true, false, conn) ||
-				pqReadData(conn) < 0)
-			{
-				result = EOF;
-				break;
-			}
-		}
-	}
-	*s = '\0';
-
-	return result;
-}
-
-/*
- * PQgetlineAsync - gets a COPY data row without blocking.
- *
- * See fe-exec.c for documentation.
- */
-int
-pqGetlineAsync2(PGconn *conn, char *buffer, int bufsize)
-{
-	int			avail;
-
-	if (conn->asyncStatus != PGASYNC_COPY_OUT)
-		return -1;				/* we are not doing a copy... */
-
-	/*
-	 * Move data from libpq's buffer to the caller's. We want to accept data
-	 * only in units of whole lines, not partial lines.  This ensures that we
-	 * can recognize the terminator line "\\.\n".  (Otherwise, if it happened
-	 * to cross a packet/buffer boundary, we might hand the first one or two
-	 * characters off to the caller, which we shouldn't.)
-	 */
-
-	conn->inCursor = conn->inStart;
-
-	avail = bufsize;
-	while (avail > 0 && conn->inCursor < conn->inEnd)
-	{
-		char		c = conn->inBuffer[conn->inCursor++];
-
-		*buffer++ = c;
-		--avail;
-		if (c == '\n')
-		{
-			/* Got a complete line; mark the data removed from libpq */
-			conn->inStart = conn->inCursor;
-			/* Is it the endmarker line? */
-			if (bufsize - avail == 3 && buffer[-3] == '\\' && buffer[-2] == '.')
-				return -1;
-			/* No, return the data line to the caller */
-			return bufsize - avail;
-		}
-	}
-
-	/*
-	 * We don't have a complete line. We'd prefer to leave it in libpq's
-	 * buffer until the rest arrives, but there is a special case: what if the
-	 * line is longer than the buffer the caller is offering us?  In that case
-	 * we'd better hand over a partial line, else we'd get into an infinite
-	 * loop. Do this in a way that ensures we can't misrecognize a terminator
-	 * line later: leave last 3 characters in libpq buffer.
-	 */
-	if (avail == 0 && bufsize > 3)
-	{
-		conn->inStart = conn->inCursor - 3;
-		return bufsize - 3;
-	}
-	return 0;
-}
-
-/*
- * PQendcopy
- *
- * See fe-exec.c for documentation.
- */
-int
-pqEndcopy2(PGconn *conn)
-{
-	PGresult   *result;
-
-	if (conn->asyncStatus != PGASYNC_COPY_IN &&
-		conn->asyncStatus != PGASYNC_COPY_OUT)
-	{
-		appendPQExpBufferStr(&conn->errorMessage,
-							 libpq_gettext("no COPY in progress\n"));
-		return 1;
-	}
-
-	/*
-	 * make sure no data is waiting to be sent, abort if we are non-blocking
-	 * and the flush fails
-	 */
-	if (pqFlush(conn) && pqIsnonblocking(conn))
-		return 1;
-
-	/* non blocking connections may have to abort at this point. */
-	if (pqIsnonblocking(conn) && PQisBusy(conn))
-		return 1;
-
-	/* Return to active duty */
-	conn->asyncStatus = PGASYNC_BUSY;
-
-	/* Wait for the completion response */
-	result = PQgetResult(conn);
-
-	/* Expecting a successful result */
-	if (result && result->resultStatus == PGRES_COMMAND_OK)
-	{
-		PQclear(result);
-		return 0;
-	}
-
-	/*
-	 * Trouble. For backwards-compatibility reasons, we issue the error
-	 * message as if it were a notice (would be nice to get rid of this
-	 * silliness, but too many apps probably don't handle errors from
-	 * PQendcopy reasonably).  Note that the app can still obtain the error
-	 * status from the PGconn object.
-	 */
-	if (conn->errorMessage.len > 0)
-	{
-		/* We have to strip the trailing newline ... pain in neck... */
-		char		svLast = conn->errorMessage.data[conn->errorMessage.len - 1];
-
-		if (svLast == '\n')
-			conn->errorMessage.data[conn->errorMessage.len - 1] = '\0';
-		pqInternalNotice(&conn->noticeHooks, "%s", conn->errorMessage.data);
-		conn->errorMessage.data[conn->errorMessage.len - 1] = svLast;
-	}
-
-	PQclear(result);
-
-	/*
-	 * The worst case is that we've lost sync with the backend entirely due to
-	 * application screwup of the copy in/out protocol. To recover, reset the
-	 * connection (talk about using a sledgehammer...)
-	 */
-	pqInternalNotice(&conn->noticeHooks,
-					 "lost synchronization with server, resetting connection");
-
-	/*
-	 * Users doing non-blocking connections need to handle the reset
-	 * themselves, they'll need to check the connection status if we return an
-	 * error.
-	 */
-	if (pqIsnonblocking(conn))
-		PQresetStart(conn);
-	else
-		PQreset(conn);
-
-	return 1;
-}
-
-
-/*
- * PQfn - Send a function call to the POSTGRES backend.
- *
- * See fe-exec.c for documentation.
- */
-PGresult *
-pqFunctionCall2(PGconn *conn, Oid fnid,
-				int *result_buf, int *actual_result_len,
-				int result_is_int,
-				const PQArgBlock *args, int nargs)
-{
-	bool		needInput = false;
-	ExecStatusType status = PGRES_FATAL_ERROR;
-	char		id;
-	int			i;
-
-	/* PQfn already validated connection state */
-
-	if (pqPutMsgStart('F', false, conn) < 0 ||	/* function call msg */
-		pqPuts(" ", conn) < 0 ||	/* dummy string */
-		pqPutInt(fnid, 4, conn) != 0 || /* function id */
-		pqPutInt(nargs, 4, conn) != 0)	/* # of args */
-	{
-		/* error message should be set up already */
-		return NULL;
-	}
-
-	for (i = 0; i < nargs; ++i)
-	{							/* len.int4 + contents	   */
-		if (pqPutInt(args[i].len, 4, conn))
-			return NULL;
-
-		if (args[i].isint)
-		{
-			if (pqPutInt(args[i].u.integer, 4, conn))
-				return NULL;
-		}
-		else
-		{
-			if (pqPutnchar((char *) args[i].u.ptr, args[i].len, conn))
-				return NULL;
-		}
-	}
-
-	if (pqPutMsgEnd(conn) < 0 ||
-		pqFlush(conn))
-		return NULL;
-
-	for (;;)
-	{
-		if (needInput)
-		{
-			/* Wait for some data to arrive (or for the channel to close) */
-			if (pqWait(true, false, conn) ||
-				pqReadData(conn) < 0)
-				break;
-		}
-
-		/*
-		 * Scan the message. If we run out of data, loop around to try again.
-		 */
-		conn->inCursor = conn->inStart;
-		needInput = true;
-
-		if (pqGetc(&id, conn))
-			continue;
-
-		/*
-		 * We should see V or E response to the command, but might get N
-		 * and/or A notices first. We also need to swallow the final Z before
-		 * returning.
-		 */
-		switch (id)
-		{
-			case 'V':			/* function result */
-				if (pqGetc(&id, conn))
-					continue;
-				if (id == 'G')
-				{
-					/* function returned nonempty value */
-					if (pqGetInt(actual_result_len, 4, conn))
-						continue;
-					if (result_is_int)
-					{
-						if (pqGetInt(result_buf, 4, conn))
-							continue;
-					}
-					else
-					{
-						if (pqGetnchar((char *) result_buf,
-									   *actual_result_len,
-									   conn))
-							continue;
-					}
-					if (pqGetc(&id, conn))	/* get the last '0' */
-						continue;
-				}
-				if (id == '0')
-				{
-					/* correctly finished function result message */
-					status = PGRES_COMMAND_OK;
-				}
-				else
-				{
-					/* The backend violates the protocol. */
-					appendPQExpBuffer(&conn->errorMessage,
-									  libpq_gettext("protocol error: id=0x%x\n"),
-									  id);
-					pqSaveErrorResult(conn);
-					conn->inStart = conn->inCursor;
-					return pqPrepareAsyncResult(conn);
-				}
-				break;
-			case 'E':			/* error return */
-				if (pqGetErrorNotice2(conn, true))
-					continue;
-				status = PGRES_FATAL_ERROR;
-				break;
-			case 'A':			/* notify message */
-				/* handle notify and go back to processing return values */
-				if (getNotify(conn))
-					continue;
-				break;
-			case 'N':			/* notice */
-				/* handle notice and go back to processing return values */
-				if (pqGetErrorNotice2(conn, false))
-					continue;
-				break;
-			case 'Z':			/* backend is ready for new query */
-				/* consume the message and exit */
-				conn->inStart = conn->inCursor;
-				/* if we saved a result object (probably an error), use it */
-				if (conn->result)
-					return pqPrepareAsyncResult(conn);
-				return PQmakeEmptyPGresult(conn, status);
-			default:
-				/* The backend violates the protocol. */
-				appendPQExpBuffer(&conn->errorMessage,
-								  libpq_gettext("protocol error: id=0x%x\n"),
-								  id);
-				pqSaveErrorResult(conn);
-				conn->inStart = conn->inCursor;
-				return pqPrepareAsyncResult(conn);
-		}
-		/* Completed this message, keep going */
-		conn->inStart = conn->inCursor;
-		needInput = false;
-	}
-
-	/*
-	 * We fall out of the loop only upon failing to read data.
-	 * conn->errorMessage has been set by pqWait or pqReadData. We want to
-	 * append it to any already-received error message.
-	 */
-	pqSaveErrorResult(conn);
-	return pqPrepareAsyncResult(conn);
-}
-
-
-/*
- * Construct startup packet
- *
- * Returns a malloc'd packet buffer, or NULL if out of memory
- */
-char *
-pqBuildStartupPacket2(PGconn *conn, int *packetlen,
-					  const PQEnvironmentOption *options)
-{
-	StartupPacket *startpacket;
-
-	*packetlen = sizeof(StartupPacket);
-	startpacket = (StartupPacket *) malloc(sizeof(StartupPacket));
-	if (!startpacket)
-		return NULL;
-
-	MemSet(startpacket, 0, sizeof(StartupPacket));
-
-	startpacket->protoVersion = pg_hton32(conn->pversion);
-
-	/* strncpy is safe here: postmaster will handle full fields correctly */
-	strncpy(startpacket->user, conn->pguser, SM_USER);
-	strncpy(startpacket->database, conn->dbName, SM_DATABASE);
-	strncpy(startpacket->tty, conn->pgtty, SM_TTY);
-
-	if (conn->pgoptions)
-		strncpy(startpacket->options, conn->pgoptions, SM_OPTIONS);
-
-	return (char *) startpacket;
-}
diff --git a/src/interfaces/libpq/fe-protocol3.c b/src/interfaces/libpq/fe-protocol3.c
index e4ee9d69d25..2ca8c057b9d 100644
--- a/src/interfaces/libpq/fe-protocol3.c
+++ b/src/interfaces/libpq/fe-protocol3.c
@@ -1843,7 +1843,7 @@ pqEndcopy3(PGconn *conn)
 	if (conn->asyncStatus == PGASYNC_COPY_IN ||
 		conn->asyncStatus == PGASYNC_COPY_BOTH)
 	{
-		if (pqPutMsgStart('c', false, conn) < 0 ||
+		if (pqPutMsgStart('c', conn) < 0 ||
 			pqPutMsgEnd(conn) < 0)
 			return 1;
 
@@ -1853,7 +1853,7 @@ pqEndcopy3(PGconn *conn)
 		 */
 		if (conn->queryclass != PGQUERY_SIMPLE)
 		{
-			if (pqPutMsgStart('S', false, conn) < 0 ||
+			if (pqPutMsgStart('S', conn) < 0 ||
 				pqPutMsgEnd(conn) < 0)
 				return 1;
 		}
@@ -1933,7 +1933,7 @@ pqFunctionCall3(PGconn *conn, Oid fnid,
 
 	/* PQfn already validated connection state */
 
-	if (pqPutMsgStart('F', false, conn) < 0 ||	/* function call msg */
+	if (pqPutMsgStart('F', conn) < 0 || /* function call msg */
 		pqPutInt(fnid, 4, conn) < 0 ||	/* function id */
 		pqPutInt(1, 2, conn) < 0 || /* # of format codes */
 		pqPutInt(1, 2, conn) < 0 || /* format code: BINARY */
diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h
index c266ad5b138..f9ca8c5cc62 100644
--- a/src/interfaces/libpq/libpq-fe.h
+++ b/src/interfaces/libpq/libpq-fe.h
@@ -60,7 +60,6 @@ typedef enum
 									 * postmaster.        */
 	CONNECTION_AUTH_OK,			/* Received authentication; waiting for
 								 * backend startup. */
-	CONNECTION_SETENV,			/* Negotiating environment. */
 	CONNECTION_SSL_STARTUP,		/* Negotiating SSL. */
 	CONNECTION_NEEDED,			/* Internal state: connect() needed */
 	CONNECTION_CHECK_WRITABLE,	/* Check if we could make a writable
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index 4db498369c7..6e831a838f2 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -232,22 +232,6 @@ typedef enum
 	PGQUERY_DESCRIBE			/* Describe Statement or Portal */
 } PGQueryClass;
 
-/* PGSetenvStatusType defines the state of the pqSetenv state machine */
-
-/* (this is used only for 2.0-protocol connections) */
-typedef enum
-{
-	SETENV_STATE_CLIENT_ENCODING_SEND,	/* About to send an Environment Option */
-	SETENV_STATE_CLIENT_ENCODING_WAIT,	/* Waiting for above send to complete */
-	SETENV_STATE_OPTION_SEND,	/* About to send an Environment Option */
-	SETENV_STATE_OPTION_WAIT,	/* Waiting for above send to complete */
-	SETENV_STATE_QUERY1_SEND,	/* About to send a status query */
-	SETENV_STATE_QUERY1_WAIT,	/* Waiting for query to complete */
-	SETENV_STATE_QUERY2_SEND,	/* About to send a status query */
-	SETENV_STATE_QUERY2_WAIT,	/* Waiting for query to complete */
-	SETENV_STATE_IDLE
-} PGSetenvStatusType;
-
 /* Typedef for the EnvironmentOptions[] array */
 typedef struct PQEnvironmentOption
 {
@@ -426,7 +410,6 @@ struct pg_conn
 	struct addrinfo *addrlist;	/* list of addresses for current connhost */
 	struct addrinfo *addr_cur;	/* the one currently being tried */
 	int			addrlist_family;	/* needed to know how to free addrlist */
-	PGSetenvStatusType setenv_state;	/* for 2.0 protocol only */
 	const PQEnvironmentOption *next_eo;
 	bool		send_appname;	/* okay to send application_name? */
 
@@ -617,22 +600,6 @@ extern void pqSaveParameterStatus(PGconn *conn, const char *name,
 extern int	pqRowProcessor(PGconn *conn, const char **errmsgp);
 extern int	PQsendQueryContinue(PGconn *conn, const char *query);
 
-/* === in fe-protocol2.c === */
-
-extern PostgresPollingStatusType pqSetenvPoll(PGconn *conn);
-
-extern char *pqBuildStartupPacket2(PGconn *conn, int *packetlen,
-								   const PQEnvironmentOption *options);
-extern void pqParseInput2(PGconn *conn);
-extern int	pqGetCopyData2(PGconn *conn, char **buffer, int async);
-extern int	pqGetline2(PGconn *conn, char *s, int maxlen);
-extern int	pqGetlineAsync2(PGconn *conn, char *buffer, int bufsize);
-extern int	pqEndcopy2(PGconn *conn);
-extern PGresult *pqFunctionCall2(PGconn *conn, Oid fnid,
-								 int *result_buf, int *actual_result_len,
-								 int result_is_int,
-								 const PQArgBlock *args, int nargs);
-
 /* === in fe-protocol3.c === */
 
 extern char *pqBuildStartupPacket3(PGconn *conn, int *packetlen,
@@ -669,7 +636,7 @@ extern int	pqSkipnchar(size_t len, PGconn *conn);
 extern int	pqPutnchar(const char *s, size_t len, PGconn *conn);
 extern int	pqGetInt(int *result, size_t bytes, PGconn *conn);
 extern int	pqPutInt(int value, size_t bytes, PGconn *conn);
-extern int	pqPutMsgStart(char msg_type, bool force_len, PGconn *conn);
+extern int	pqPutMsgStart(char msg_type, PGconn *conn);
 extern int	pqPutMsgEnd(PGconn *conn);
 extern int	pqReadData(PGconn *conn);
 extern int	pqFlush(PGconn *conn);
-- 
2.30.0

0002-Simplify-COPY-FROM-parsing-by-forcing-lookahead.patchtext/x-patch; charset=UTF-8; name=0002-Simplify-COPY-FROM-parsing-by-forcing-lookahead.patchDownload
From cd9cb916b340ed7f197af0f4ab87e4bb16667f41 Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <heikki.linnakangas@iki.fi>
Date: Thu, 4 Feb 2021 12:39:40 +0200
Subject: [PATCH 2/2] Simplify COPY FROM parsing by forcing lookahead.

Now that we don't support the old-style COPY protocol anymore, we can
force four bytes of lookahead like was suggested in an existing comment,
and simplify the loop in CopyReadLineText().

Discussion: https://www.postgresql.org/message-id/9ec25819-0a8a-d51a-17dc-4150bb3cca3b%40iki.fi
---
 src/backend/commands/copyfromparse.c     | 119 ++++++++---------------
 src/include/commands/copyfrom_internal.h |   3 +-
 2 files changed, 40 insertions(+), 82 deletions(-)

diff --git a/src/backend/commands/copyfromparse.c b/src/backend/commands/copyfromparse.c
index dd629668540..757b240a3c8 100644
--- a/src/backend/commands/copyfromparse.c
+++ b/src/backend/commands/copyfromparse.c
@@ -46,21 +46,6 @@
  * empty statements.  See http://www.cit.gu.edu.au/~anthony/info/C/C.macros.
  */
 
-/*
- * This keeps the character read at the top of the loop in the buffer
- * even if there is more than one read-ahead.
- */
-#define IF_NEED_REFILL_AND_NOT_EOF_CONTINUE(extralen) \
-if (1) \
-{ \
-	if (raw_buf_ptr + (extralen) >= copy_buf_len && !hit_eof) \
-	{ \
-		raw_buf_ptr = prev_raw_ptr; /* undo fetch */ \
-		need_data = true; \
-		continue; \
-	} \
-} else ((void) 0)
-
 /* This consumes the remainder of the buffer and breaks */
 #define IF_NEED_REFILL_AND_EOF_BREAK(extralen) \
 if (1) \
@@ -118,7 +103,7 @@ static int	CopyGetData(CopyFromState cstate, void *databuf,
 						int minread, int maxread);
 static inline bool CopyGetInt32(CopyFromState cstate, int32 *val);
 static inline bool CopyGetInt16(CopyFromState cstate, int16 *val);
-static bool CopyLoadRawBuf(CopyFromState cstate);
+static bool CopyLoadRawBuf(CopyFromState cstate, int minread);
 static int	CopyReadBinaryData(CopyFromState cstate, char *dest, int nbytes);
 
 void
@@ -209,7 +194,7 @@ CopyGetData(CopyFromState cstate, void *databuf, int minread, int maxread)
 				ereport(ERROR,
 						(errcode_for_file_access(),
 						 errmsg("could not read from COPY file: %m")));
-			if (bytesread == 0)
+			if (bytesread < maxread)
 				cstate->reached_eof = true;
 			break;
 		case COPY_FRONTEND:
@@ -278,6 +263,8 @@ CopyGetData(CopyFromState cstate, void *databuf, int minread, int maxread)
 			break;
 		case COPY_CALLBACK:
 			bytesread = cstate->data_source_cb(databuf, minread, maxread);
+			if (bytesread < minread)
+				cstate->reached_eof = true;
 			break;
 	}
 
@@ -329,14 +316,13 @@ CopyGetInt16(CopyFromState cstate, int16 *val)
 /*
  * CopyLoadRawBuf loads some more data into raw_buf
  *
- * Returns true if able to obtain at least one more byte, else false.
+ * Returns true if able to obtain at least 'minread' bytes, else false.
  *
  * If RAW_BUF_BYTES(cstate) > 0, the unprocessed bytes are moved to the start
- * of the buffer and then we load more data after that.  This case occurs only
- * when a multibyte character crosses a bufferload boundary.
+ * of the buffer and then we load more data after that.
  */
 static bool
-CopyLoadRawBuf(CopyFromState cstate)
+CopyLoadRawBuf(CopyFromState cstate, int minread)
 {
 	int			nbytes = RAW_BUF_BYTES(cstate);
 	int			inbytes;
@@ -347,14 +333,15 @@ CopyLoadRawBuf(CopyFromState cstate)
 				nbytes);
 
 	inbytes = CopyGetData(cstate, cstate->raw_buf + nbytes,
-						  1, RAW_BUF_SIZE - nbytes);
+						  minread, RAW_BUF_SIZE - nbytes);
 	nbytes += inbytes;
 	cstate->raw_buf[nbytes] = '\0';
 	cstate->raw_buf_index = 0;
 	cstate->raw_buf_len = nbytes;
 	cstate->bytes_processed += nbytes;
 	pgstat_progress_update_param(PROGRESS_COPY_BYTES_PROCESSED, cstate->bytes_processed);
-	return (inbytes > 0);
+
+	return (inbytes >= minread);
 }
 
 /*
@@ -389,7 +376,7 @@ CopyReadBinaryData(CopyFromState cstate, char *dest, int nbytes)
 			/* Load more data if buffer is empty. */
 			if (RAW_BUF_BYTES(cstate) == 0)
 			{
-				if (!CopyLoadRawBuf(cstate))
+				if (!CopyLoadRawBuf(cstate, 1))
 					break;		/* EOF */
 			}
 
@@ -678,7 +665,7 @@ CopyReadLine(CopyFromState cstate)
 			do
 			{
 				cstate->raw_buf_index = cstate->raw_buf_len;
-			} while (CopyLoadRawBuf(cstate));
+			} while (CopyLoadRawBuf(cstate, 1));
 		}
 	}
 	else
@@ -747,7 +734,6 @@ CopyReadLineText(CopyFromState cstate)
 	char	   *copy_raw_buf;
 	int			raw_buf_ptr;
 	int			copy_buf_len;
-	bool		need_data = false;
 	bool		hit_eof = false;
 	bool		result = false;
 	char		mblen_str[2];
@@ -794,6 +780,7 @@ CopyReadLineText(CopyFromState cstate)
 	copy_raw_buf = cstate->raw_buf;
 	raw_buf_ptr = cstate->raw_buf_index;
 	copy_buf_len = cstate->raw_buf_len;
+	hit_eof = cstate->reached_eof;
 
 	for (;;)
 	{
@@ -801,38 +788,40 @@ CopyReadLineText(CopyFromState cstate)
 		char		c;
 
 		/*
-		 * Load more data if needed.  Ideally we would just force four bytes
-		 * of read-ahead and avoid the many calls to
-		 * IF_NEED_REFILL_AND_NOT_EOF_CONTINUE(), but the COPY_OLD_FE protocol
-		 * does not allow us to read too far ahead or we might read into the
-		 * next data, so we read-ahead only as far we know we can.  One
-		 * optimization would be to read-ahead four byte here if
-		 * cstate->copy_src != COPY_OLD_FE, but it hardly seems worth it,
-		 * considering the size of the buffer.
+		 * Load more data if needed.
+		 *
+		 * We need to look ahead max three bytes in one iteration of the loop
+		 * (for the sequence \.<CR><NL>), so make sure we have at least four
+		 * bytes in the buffer.  Note that we always guarantee that there is
+		 * one \0 in the buffer, after last valid byte; the lookaheads below
+		 * rely on that.
 		 */
-		if (raw_buf_ptr >= copy_buf_len || need_data)
+#define COPY_READ_LINE_LOOKAHEAD	4
+		if (raw_buf_ptr + COPY_READ_LINE_LOOKAHEAD >= copy_buf_len)
 		{
-			REFILL_LINEBUF;
+			if (!hit_eof)
+			{
+				REFILL_LINEBUF;
 
-			/*
-			 * Try to read some more data.  This will certainly reset
-			 * raw_buf_index to zero, and raw_buf_ptr must go with it.
-			 */
-			if (!CopyLoadRawBuf(cstate))
-				hit_eof = true;
-			raw_buf_ptr = 0;
-			copy_buf_len = cstate->raw_buf_len;
+				/*
+				 * Try to read some more data.  This will certainly reset
+				 * raw_buf_index to zero, and raw_buf_ptr must go with it.
+				 */
+				if (!CopyLoadRawBuf(cstate, COPY_READ_LINE_LOOKAHEAD))
+					hit_eof = true;
+				raw_buf_ptr = 0;
+				copy_buf_len = cstate->raw_buf_len;
+			}
 
 			/*
 			 * If we are completely out of data, break out of the loop,
 			 * reporting EOF.
 			 */
-			if (copy_buf_len <= 0)
+			if (copy_buf_len - raw_buf_ptr <= 0)
 			{
 				result = true;
 				break;
 			}
-			need_data = false;
 		}
 
 		/* OK to fetch a character */
@@ -841,20 +830,6 @@ CopyReadLineText(CopyFromState cstate)
 
 		if (cstate->opts.csv_mode)
 		{
-			/*
-			 * If character is '\\' or '\r', we may need to look ahead below.
-			 * Force fetch of the next character if we don't already have it.
-			 * We need to do this before changing CSV state, in case one of
-			 * these characters is also the quote or escape character.
-			 *
-			 * Note: old-protocol does not like forced prefetch, but it's OK
-			 * here since we cannot validly be at EOF.
-			 */
-			if (c == '\\' || c == '\r')
-			{
-				IF_NEED_REFILL_AND_NOT_EOF_CONTINUE(0);
-			}
-
 			/*
 			 * Dealing with quotes and escapes here is mildly tricky. If the
 			 * quote char is also the escape char, there's no problem - we
@@ -888,14 +863,9 @@ CopyReadLineText(CopyFromState cstate)
 				cstate->eol_type == EOL_CRNL)
 			{
 				/*
-				 * If need more data, go back to loop top to load it.
-				 *
-				 * Note that if we are at EOF, c will wind up as '\0' because
-				 * of the guaranteed pad of raw_buf.
+				 * Look at the next character.  If we're at EOF, c2 will wind
+				 * up as '\0' because of the guaranteed pad of raw_buf.
 				 */
-				IF_NEED_REFILL_AND_NOT_EOF_CONTINUE(0);
-
-				/* get next char */
 				c = copy_raw_buf[raw_buf_ptr];
 
 				if (c == '\n')
@@ -961,7 +931,6 @@ CopyReadLineText(CopyFromState cstate)
 		{
 			char		c2;
 
-			IF_NEED_REFILL_AND_NOT_EOF_CONTINUE(0);
 			IF_NEED_REFILL_AND_EOF_BREAK(0);
 
 			/* -----
@@ -976,16 +945,9 @@ CopyReadLineText(CopyFromState cstate)
 			{
 				raw_buf_ptr++;	/* consume the '.' */
 
-				/*
-				 * Note: if we loop back for more data here, it does not
-				 * matter that the CSV state change checks are re-executed; we
-				 * will come back here with no important state changed.
-				 */
 				if (cstate->eol_type == EOL_CRNL)
 				{
-					/* Get the next character */
-					IF_NEED_REFILL_AND_NOT_EOF_CONTINUE(0);
-					/* if hit_eof, c2 will become '\0' */
+					/* Get next character.  If hit_eof, c2 will become '\0' */
 					c2 = copy_raw_buf[raw_buf_ptr++];
 
 					if (c2 == '\n')
@@ -1008,9 +970,7 @@ CopyReadLineText(CopyFromState cstate)
 					}
 				}
 
-				/* Get the next character */
-				IF_NEED_REFILL_AND_NOT_EOF_CONTINUE(0);
-				/* if hit_eof, c2 will become '\0' */
+				/* Get next character.  If hit_eof, c2 will become '\0' */
 				c2 = copy_raw_buf[raw_buf_ptr++];
 
 				if (c2 != '\r' && c2 != '\n')
@@ -1087,7 +1047,6 @@ not_end_of_copy:
 			mblen_str[0] = c;
 			mblen = pg_encoding_mblen(cstate->file_encoding, mblen_str);
 
-			IF_NEED_REFILL_AND_NOT_EOF_CONTINUE(mblen - 1);
 			IF_NEED_REFILL_AND_EOF_BREAK(mblen - 1);
 			raw_buf_ptr += mblen - 1;
 		}
diff --git a/src/include/commands/copyfrom_internal.h b/src/include/commands/copyfrom_internal.h
index 705f5b615be..c088c4facdb 100644
--- a/src/include/commands/copyfrom_internal.h
+++ b/src/include/commands/copyfrom_internal.h
@@ -70,8 +70,7 @@ typedef struct CopyFromStateData
 	CopySource	copy_src;		/* type of copy source */
 	FILE	   *copy_file;		/* used if copy_src == COPY_FILE */
 	StringInfo	fe_msgbuf;		/* used if copy_src == COPY_NEW_FE */
-	bool		reached_eof;	/* true if we read to end of copy data (not
-								 * all copy_src types maintain this) */
+	bool		reached_eof;	/* true if we read to end of copy data */
 
 	EolType		eol_type;		/* EOL type of input */
 	int			file_encoding;	/* file or remote side's character encoding */
-- 
2.30.0

#9Alvaro Herrera
alvherre@alvh.no-ip.org
In reply to: Heikki Linnakangas (#8)
Re: Removing support for COPY FROM STDIN in protocol version 2

On 2021-Feb-04, Heikki Linnakangas wrote:

On 04/02/2021 08:54, Michael Paquier wrote:

On Wed, Feb 03, 2021 at 11:29:37AM -0500, Tom Lane wrote:

Then let's kill it dead, server and libpq both.

Yeah.

Ok, here we go.

Are you going to bump the .so version for this? I think that should be
done, since some functions disappear and there are struct changes. It
is curious, though, to see that exports.txt needs no changes.

(I'm not sure what's our protocol for so-version changes. Do we wait
till end of cycle, or do we put it together with the commit that
modifies the library? src/tools/RELEASE_CHANGES doesn't say)

--
�lvaro Herrera Valdivia, Chile

#10Tom Lane
tgl@sss.pgh.pa.us
In reply to: Alvaro Herrera (#9)
Re: Removing support for COPY FROM STDIN in protocol version 2

Alvaro Herrera <alvherre@alvh.no-ip.org> writes:

On 2021-Feb-04, Heikki Linnakangas wrote:

Ok, here we go.

Are you going to bump the .so version for this? I think that should be
done, since some functions disappear and there are struct changes. It
is curious, though, to see that exports.txt needs no changes.

Uh, what? There should be no externally visible ABI changes in libpq
(he says without having read the patch). If there's a need for a library
major version bump, that'd be sufficient reason not to do this IMO.

regards, tom lane

#11Alvaro Herrera
alvherre@alvh.no-ip.org
In reply to: Tom Lane (#10)
Re: Removing support for COPY FROM STDIN in protocol version 2

On 2021-Feb-04, Tom Lane wrote:

Alvaro Herrera <alvherre@alvh.no-ip.org> writes:

On 2021-Feb-04, Heikki Linnakangas wrote:

Ok, here we go.

Are you going to bump the .so version for this? I think that should be
done, since some functions disappear and there are struct changes. It
is curious, though, to see that exports.txt needs no changes.

Uh, what? There should be no externally visible ABI changes in libpq
(he says without having read the patch). If there's a need for a library
major version bump, that'd be sufficient reason not to do this IMO.

Yeah, the changes I was thinking about are all in libpq-int.h so that's
not really a problem. But one enum in libpq-fe.h renumbers values, and
I think it's better to keep the old value labelled as "unused" to avoid
any changes.

--
�lvaro Herrera Valdivia, Chile

#12Tom Lane
tgl@sss.pgh.pa.us
In reply to: Alvaro Herrera (#11)
Re: Removing support for COPY FROM STDIN in protocol version 2

Alvaro Herrera <alvherre@alvh.no-ip.org> writes:

Yeah, the changes I was thinking about are all in libpq-int.h so that's
not really a problem. But one enum in libpq-fe.h renumbers values, and
I think it's better to keep the old value labelled as "unused" to avoid
any changes.

Oh, yeah, can't do that. libpq-fe.h probably shouldn't change at all;
but certainly we can't renumber existing enum values there.

regards, tom lane

#13Heikki Linnakangas
hlinnaka@iki.fi
In reply to: Tom Lane (#12)
Re: Removing support for COPY FROM STDIN in protocol version 2

On 04/02/2021 17:35, Tom Lane wrote:

Alvaro Herrera <alvherre@alvh.no-ip.org> writes:

Yeah, the changes I was thinking about are all in libpq-int.h so that's
not really a problem. But one enum in libpq-fe.h renumbers values, and
I think it's better to keep the old value labelled as "unused" to avoid
any changes.

Oh, yeah, can't do that. libpq-fe.h probably shouldn't change at all;
but certainly we can't renumber existing enum values there.

Ah, right, there's even a comment above the enum that says that's a no
no. But yeah, fixing that, I see no need for .so version bump.

- Heikki

#14John Naylor
john.naylor@enterprisedb.com
In reply to: Heikki Linnakangas (#13)
Re: Removing support for COPY FROM STDIN in protocol version 2

On Thu, Feb 4, 2021 at 11:47 AM Heikki Linnakangas <hlinnaka@iki.fi> wrote:

On 04/02/2021 17:35, Tom Lane wrote:

Alvaro Herrera <alvherre@alvh.no-ip.org> writes:

Yeah, the changes I was thinking about are all in libpq-int.h so that's
not really a problem. But one enum in libpq-fe.h renumbers values, and
I think it's better to keep the old value labelled as "unused" to avoid
any changes.

Oh, yeah, can't do that. libpq-fe.h probably shouldn't change at all;
but certainly we can't renumber existing enum values there.

Ah, right, there's even a comment above the enum that says that's a no
no. But yeah, fixing that, I see no need for .so version bump.

I was able to build libpq and psql on 7.3 with the tooling found on RHEL 7
(the rest of the tree refused to build, but that's not relevant here) and
got the expected message when trying to connect:

master:
Welcome to psql 7.3.21, the PostgreSQL interactive terminal.

patch:
psql: FATAL: unsupported frontend protocol 2.0: server supports 3.0 to 3.0

I couldn't find any traces of version 2 in the tree with the patch applied.
The enum mentioned above seems the only issue that needs to be fixed before
commit.

--
John Naylor
EDB: http://www.enterprisedb.com

#15Tom Lane
tgl@sss.pgh.pa.us
In reply to: Heikki Linnakangas (#8)
1 attachment(s)
Re: Removing support for COPY FROM STDIN in protocol version 2

Heikki Linnakangas <hlinnaka@iki.fi> writes:

On 04/02/2021 08:54, Michael Paquier wrote:

On Wed, Feb 03, 2021 at 11:29:37AM -0500, Tom Lane wrote:

Then let's kill it dead, server and libpq both.

Ok, here we go.

One interesting thing I noticed while doing this:

Up until now, we always used the old protocol for errors that happened
early in backend startup, before we processed the client's protocol
version and set the FrontendProtocol variable. I'm sure that made sense
when V3 was introduced, but it was a surprise to me, and I didn't find
that documented anywhere. I changed it so that we use V3 errors, if
FrontendProtocol is not yet set.

However, I kept rudimentary support for sending errors in protocol
version 2. This way, if a client tries to connect with an old client, we
still send the "unsupported frontend protocol" error in the old format.
Likewise, I kept the code in libpq to understand v2 ErrorResponse
messages during authentication.

Yeah, we clearly need to send the "unsupported frontend protocol" error
in as old a protocol as we can. Another point here is that if the
postmaster fails to fork() a child process, it has a hack to spit out
an error message without using backend/libpq at all, and that sends
in 2.0 protocol. IIRC that's partly because it's simpler, as well
as backward-friendly. So we should keep these vestiges.

I rebased the 0001 patch (it'd bit-rotted slightly), read it over,
and did some light testing. I found a couple of other places where
we could drop code: any client-side code that has to act differently
for pre-7.4 servers can lose that option, because it'll never be
talking to one of those now.

Patched psql, trying to connect to a 7.3 server, reports this:

$ psql -h ...
psql: error: connection to server at "sss2" (192.168.1.3), port 5432 failed: FATAL: unsupported frontend protocol

$

Conversely, 7.3 psql trying to connect to a patched server reports:

$ psql -h ...
psql: FATAL: unsupported frontend protocol 2.0: server supports 3.0 to 3.0

$

I'm not sure where the extra newlines are coming from, and it seems
unlikely to be worth worrying over. This behavior is good enough for me.

I concur that 0001 attached is committable. I have not looked at
your 0002, though.

regards, tom lane

Attachments:

0001-remove-protocol-2.0-support.patchtext/x-diff; charset=us-ascii; name=0001-remove-protocol-2.0-support.patchDownload
diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index cc20b0f234..0553279314 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -2274,20 +2274,6 @@ const char *PQparameterStatus(const PGconn *conn, const char *paramName);
        cannot change after startup.
       </para>
 
-      <para>
-       Pre-3.0-protocol servers do not report parameter settings, but
-       <application>libpq</application> includes logic to obtain values for
-       <varname>server_version</varname> and <varname>client_encoding</varname> anyway.
-       Applications are encouraged to use <xref linkend="libpq-PQparameterStatus"/>
-       rather than <foreignphrase>ad hoc</foreignphrase> code to determine these values.
-       (Beware however that on a pre-3.0 connection, changing
-       <varname>client_encoding</varname> via <command>SET</command> after connection
-       startup will not be reflected by <xref linkend="libpq-PQparameterStatus"/>.)
-       For <varname>server_version</varname>, see also
-       <xref linkend="libpq-PQserverVersion"/>, which returns the information in a
-       numeric form that is much easier to compare against.
-      </para>
-
       <para>
        If no value for <varname>standard_conforming_strings</varname> is reported,
        applications can assume it is <literal>off</literal>, that is, backslashes
@@ -2314,15 +2300,12 @@ const char *PQparameterStatus(const PGconn *conn, const char *paramName);
 int PQprotocolVersion(const PGconn *conn);
 </synopsis>
        Applications might wish to use this function to determine whether certain
-       features are supported.  Currently, the possible values are 2 (2.0
-       protocol), 3 (3.0 protocol), or zero (connection bad).  The
-       protocol version will
+       features are supported.  Currently, the possible values are 3
+       (3.0 protocol), or zero (connection bad).  The protocol version will
        not change after connection startup is complete, but it could
-       theoretically change during a connection reset.  The 3.0 protocol
-       will normally be used when communicating with
-       <productname>PostgreSQL</productname> 7.4 or later servers; pre-7.4 servers
-       support only protocol 2.0.  (Protocol 1.0 is obsolete and not
-       supported by <application>libpq</application>.)
+       theoretically change during a connection reset.  The 3.0 protocol is
+       supported by <productname>PostgreSQL</productname> server versions 7.4
+       and above.
       </para>
      </listitem>
     </varlistentry>
@@ -2739,8 +2722,7 @@ PGresult *PQexecParams(PGconn *conn,
         <xref linkend="libpq-PQexecParams"/> is like <xref linkend="libpq-PQexec"/>, but offers additional
         functionality: parameter values can be specified separately from the command
         string proper, and query results can be requested in either text or binary
-        format.  <xref linkend="libpq-PQexecParams"/> is supported only in protocol 3.0 and later
-        connections; it will fail when using protocol 2.0.
+        format.
        </para>
 
        <para>
@@ -2917,8 +2899,6 @@ PGresult *PQprepare(PGconn *conn,
         execution with <xref linkend="libpq-PQexecPrepared"/>.  This feature allows
         commands to be executed repeatedly without being parsed and
         planned each time;  see <xref linkend="sql-prepare"/> for details.
-        <xref linkend="libpq-PQprepare"/> is supported only in protocol 3.0 and later
-        connections; it will fail when using protocol 2.0.
        </para>
 
        <para>
@@ -2992,9 +2972,7 @@ PGresult *PQexecPrepared(PGconn *conn,
         This feature allows commands that will be used repeatedly to be
         parsed and planned just once, rather than each time they are
         executed.  The statement must have been prepared previously in
-        the current session.  <xref linkend="libpq-PQexecPrepared"/> is supported
-        only in protocol 3.0 and later connections; it will fail when
-        using protocol 2.0.
+        the current session.
        </para>
 
        <para>
@@ -3021,8 +2999,6 @@ PGresult *PQdescribePrepared(PGconn *conn, const char *stmtName);
        <para>
         <xref linkend="libpq-PQdescribePrepared"/> allows an application to obtain
         information about a previously prepared statement.
-        <xref linkend="libpq-PQdescribePrepared"/> is supported only in protocol 3.0
-        and later connections; it will fail when using protocol 2.0.
        </para>
 
        <para>
@@ -3059,8 +3035,6 @@ PGresult *PQdescribePortal(PGconn *conn, const char *portalName);
         (<application>libpq</application> does not provide any direct access to
         portals, but you can use this function to inspect the properties
         of a cursor created with a <command>DECLARE CURSOR</command> SQL command.)
-        <xref linkend="libpq-PQdescribePortal"/> is supported only in protocol 3.0
-        and later connections; it will fail when using protocol 2.0.
        </para>
 
        <para>
@@ -3566,8 +3540,6 @@ char *PQresultErrorField(const PGresult *res, int fieldcode);
        <para>
         Errors generated internally by <application>libpq</application> will
         have severity and primary message, but typically no other fields.
-        Errors returned by a pre-3.0-protocol server will include severity and
-        primary message, and sometimes a detail message, but no other fields.
        </para>
 
        <para>
@@ -3728,8 +3700,7 @@ Oid PQftable(const PGresult *res,
 
       <para>
        <literal>InvalidOid</literal> is returned if the column number is out of range,
-       or if the specified column is not a simple reference to a table column,
-       or when using pre-3.0 protocol.
+       or if the specified column is not a simple reference to a table column.
        You can query the system table <literal>pg_class</literal> to determine
        exactly which table is referenced.
       </para>
@@ -3759,8 +3730,7 @@ int PQftablecol(const PGresult *res,
 
       <para>
        Zero is returned if the column number is out of range, or if the
-       specified column is not a simple reference to a table column, or
-       when using pre-3.0 protocol.
+       specified column is not a simple reference to a table column.
       </para>
      </listitem>
     </varlistentry>
@@ -4593,8 +4563,8 @@ int PQsendQueryParams(PGconn *conn,
        query parameters can be specified separately from the query string.
        The function's parameters are handled identically to
        <xref linkend="libpq-PQexecParams"/>.  Like
-       <xref linkend="libpq-PQexecParams"/>, it will not work on 2.0-protocol
-       connections, and it allows only one command in the query string.
+       <xref linkend="libpq-PQexecParams"/>, it allows only one command in the
+       query string.
       </para>
      </listitem>
     </varlistentry>
@@ -4619,9 +4589,7 @@ int PQsendPrepare(PGconn *conn,
        After a successful call, call <xref linkend="libpq-PQgetResult"/> to
        determine whether the server successfully created the prepared
        statement.  The function's parameters are handled identically to
-       <xref linkend="libpq-PQprepare"/>.  Like
-       <xref linkend="libpq-PQprepare"/>, it will not work on 2.0-protocol
-       connections.
+       <xref linkend="libpq-PQprepare"/>.
       </para>
      </listitem>
     </varlistentry>
@@ -4647,9 +4615,7 @@ int PQsendQueryPrepared(PGconn *conn,
        the command to be executed is specified by naming a
        previously-prepared statement, instead of giving a query string.
        The function's parameters are handled identically to
-       <xref linkend="libpq-PQexecPrepared"/>.  Like
-       <xref linkend="libpq-PQexecPrepared"/>, it will not work on
-       2.0-protocol connections.
+       <xref linkend="libpq-PQexecPrepared"/>.
       </para>
      </listitem>
     </varlistentry>
@@ -4669,9 +4635,7 @@ int PQsendDescribePrepared(PGconn *conn, const char *stmtName);
        it returns 1 if it was able to dispatch the request, and 0 if not.
        After a successful call, call <xref linkend="libpq-PQgetResult"/> to
        obtain the results.  The function's parameters are handled
-       identically to <xref linkend="libpq-PQdescribePrepared"/>.  Like
-       <xref linkend="libpq-PQdescribePrepared"/>, it will not work on
-       2.0-protocol connections.
+       identically to <xref linkend="libpq-PQdescribePrepared"/>.
       </para>
      </listitem>
     </varlistentry>
@@ -4691,9 +4655,7 @@ int PQsendDescribePortal(PGconn *conn, const char *portalName);
        it returns 1 if it was able to dispatch the request, and 0 if not.
        After a successful call, call <xref linkend="libpq-PQgetResult"/> to
        obtain the results.  The function's parameters are handled
-       identically to <xref linkend="libpq-PQdescribePortal"/>.  Like
-       <xref linkend="libpq-PQdescribePortal"/>, it will not work on
-       2.0-protocol connections.
+       identically to <xref linkend="libpq-PQdescribePortal"/>.
       </para>
      </listitem>
     </varlistentry>
@@ -5460,13 +5422,6 @@ typedef struct pgNotify
    </variablelist>
   </para>
 
-  <note>
-   <para>
-    These additional data values are only available when using protocol
-    3.0.  When using protocol 2.0, all these functions will return 0.
-   </para>
-  </note>
-
   <sect2 id="libpq-copy-send">
    <title>Functions for Sending <command>COPY</command> Data</title>
 
@@ -5531,9 +5486,7 @@ int PQputCopyEnd(PGconn *conn,
        <parameter>errormsg</parameter> used as the error message.  (One should not
        assume that this exact error message will come back from the server,
        however, as the server might have already failed the
-       <command>COPY</command> for its own reasons.  Also note that the option
-       to force failure does not work when using pre-3.0-protocol
-       connections.)
+       <command>COPY</command> for its own reasons.)
       </para>
 
       <para>
diff --git a/src/backend/access/common/printtup.c b/src/backend/access/common/printtup.c
index 4468480e9b..54b539f6fb 100644
--- a/src/backend/access/common/printtup.c
+++ b/src/backend/access/common/printtup.c
@@ -27,16 +27,9 @@
 static void printtup_startup(DestReceiver *self, int operation,
 							 TupleDesc typeinfo);
 static bool printtup(TupleTableSlot *slot, DestReceiver *self);
-static bool printtup_20(TupleTableSlot *slot, DestReceiver *self);
-static bool printtup_internal_20(TupleTableSlot *slot, DestReceiver *self);
 static void printtup_shutdown(DestReceiver *self);
 static void printtup_destroy(DestReceiver *self);
 
-static void SendRowDescriptionCols_2(StringInfo buf, TupleDesc typeinfo,
-									 List *targetlist, int16 *formats);
-static void SendRowDescriptionCols_3(StringInfo buf, TupleDesc typeinfo,
-									 List *targetlist, int16 *formats);
-
 /* ----------------------------------------------------------------
  *		printtup / debugtup support
  * ----------------------------------------------------------------
@@ -112,19 +105,6 @@ SetRemoteDestReceiverParams(DestReceiver *self, Portal portal)
 		   myState->pub.mydest == DestRemoteExecute);
 
 	myState->portal = portal;
-
-	if (PG_PROTOCOL_MAJOR(FrontendProtocol) < 3)
-	{
-		/*
-		 * In protocol 2.0 the Bind message does not exist, so there is no way
-		 * for the columns to have different print formats; it's sufficient to
-		 * look at the first one.
-		 */
-		if (portal->formats && portal->formats[0] != 0)
-			myState->pub.receiveSlot = printtup_internal_20;
-		else
-			myState->pub.receiveSlot = printtup_20;
-	}
 }
 
 static void
@@ -149,21 +129,6 @@ printtup_startup(DestReceiver *self, int operation, TupleDesc typeinfo)
 												"printtup",
 												ALLOCSET_DEFAULT_SIZES);
 
-	if (PG_PROTOCOL_MAJOR(FrontendProtocol) < 3)
-	{
-		/*
-		 * Send portal name to frontend (obsolete cruft, gone in proto 3.0)
-		 *
-		 * If portal name not specified, use "blank" portal.
-		 */
-		const char *portalName = portal->name;
-
-		if (portalName == NULL || portalName[0] == '\0')
-			portalName = "blank";
-
-		pq_puttextmessage('P', portalName);
-	}
-
 	/*
 	 * If we are supposed to emit row descriptions, then send the tuple
 	 * descriptor of the tuples.
@@ -202,31 +167,14 @@ SendRowDescriptionMessage(StringInfo buf, TupleDesc typeinfo,
 						  List *targetlist, int16 *formats)
 {
 	int			natts = typeinfo->natts;
-	int			proto = PG_PROTOCOL_MAJOR(FrontendProtocol);
+	int			i;
+	ListCell   *tlist_item = list_head(targetlist);
 
 	/* tuple descriptor message type */
 	pq_beginmessage_reuse(buf, 'T');
 	/* # of attrs in tuples */
 	pq_sendint16(buf, natts);
 
-	if (proto >= 3)
-		SendRowDescriptionCols_3(buf, typeinfo, targetlist, formats);
-	else
-		SendRowDescriptionCols_2(buf, typeinfo, targetlist, formats);
-
-	pq_endmessage_reuse(buf);
-}
-
-/*
- * Send description for each column when using v3+ protocol
- */
-static void
-SendRowDescriptionCols_3(StringInfo buf, TupleDesc typeinfo, List *targetlist, int16 *formats)
-{
-	int			natts = typeinfo->natts;
-	int			i;
-	ListCell   *tlist_item = list_head(targetlist);
-
 	/*
 	 * Preallocate memory for the entire message to be sent. That allows to
 	 * use the significantly faster inline pqformat.h functions and to avoid
@@ -291,33 +239,8 @@ SendRowDescriptionCols_3(StringInfo buf, TupleDesc typeinfo, List *targetlist, i
 		pq_writeint32(buf, atttypmod);
 		pq_writeint16(buf, format);
 	}
-}
-
-/*
- * Send description for each column when using v2 protocol
- */
-static void
-SendRowDescriptionCols_2(StringInfo buf, TupleDesc typeinfo, List *targetlist, int16 *formats)
-{
-	int			natts = typeinfo->natts;
-	int			i;
-
-	for (i = 0; i < natts; ++i)
-	{
-		Form_pg_attribute att = TupleDescAttr(typeinfo, i);
-		Oid			atttypid = att->atttypid;
-		int32		atttypmod = att->atttypmod;
 
-		/* If column is a domain, send the base type and typmod instead */
-		atttypid = getBaseTypeAndTypmod(atttypid, &atttypmod);
-
-		pq_sendstring(buf, NameStr(att->attname));
-		/* column ID only info appears in protocol 3.0 and up */
-		pq_sendint32(buf, atttypid);
-		pq_sendint16(buf, att->attlen);
-		pq_sendint32(buf, atttypmod);
-		/* format info only appears in protocol 3.0 and up */
-	}
+	pq_endmessage_reuse(buf);
 }
 
 /*
@@ -371,7 +294,7 @@ printtup_prepare_info(DR_printtup *myState, TupleDesc typeinfo, int numAttrs)
 }
 
 /* ----------------
- *		printtup --- print a tuple in protocol 3.0
+ *		printtup --- send a tuple to the client
  * ----------------
  */
 static bool
@@ -455,84 +378,6 @@ printtup(TupleTableSlot *slot, DestReceiver *self)
 	return true;
 }
 
-/* ----------------
- *		printtup_20 --- print a tuple in protocol 2.0
- * ----------------
- */
-static bool
-printtup_20(TupleTableSlot *slot, DestReceiver *self)
-{
-	TupleDesc	typeinfo = slot->tts_tupleDescriptor;
-	DR_printtup *myState = (DR_printtup *) self;
-	MemoryContext oldcontext;
-	StringInfo	buf = &myState->buf;
-	int			natts = typeinfo->natts;
-	int			i,
-				j,
-				k;
-
-	/* Set or update my derived attribute info, if needed */
-	if (myState->attrinfo != typeinfo || myState->nattrs != natts)
-		printtup_prepare_info(myState, typeinfo, natts);
-
-	/* Make sure the tuple is fully deconstructed */
-	slot_getallattrs(slot);
-
-	/* Switch into per-row context so we can recover memory below */
-	oldcontext = MemoryContextSwitchTo(myState->tmpcontext);
-
-	/*
-	 * tell the frontend to expect new tuple data (in ASCII style)
-	 */
-	pq_beginmessage_reuse(buf, 'D');
-
-	/*
-	 * send a bitmap of which attributes are not null
-	 */
-	j = 0;
-	k = 1 << 7;
-	for (i = 0; i < natts; ++i)
-	{
-		if (!slot->tts_isnull[i])
-			j |= k;				/* set bit if not null */
-		k >>= 1;
-		if (k == 0)				/* end of byte? */
-		{
-			pq_sendint8(buf, j);
-			j = 0;
-			k = 1 << 7;
-		}
-	}
-	if (k != (1 << 7))			/* flush last partial byte */
-		pq_sendint8(buf, j);
-
-	/*
-	 * send the attributes of this tuple
-	 */
-	for (i = 0; i < natts; ++i)
-	{
-		PrinttupAttrInfo *thisState = myState->myinfo + i;
-		Datum		attr = slot->tts_values[i];
-		char	   *outputstr;
-
-		if (slot->tts_isnull[i])
-			continue;
-
-		Assert(thisState->format == 0);
-
-		outputstr = OutputFunctionCall(&thisState->finfo, attr);
-		pq_sendcountedtext(buf, outputstr, strlen(outputstr), true);
-	}
-
-	pq_endmessage_reuse(buf);
-
-	/* Return to caller's context, and flush row's temporary memory */
-	MemoryContextSwitchTo(oldcontext);
-	MemoryContextReset(myState->tmpcontext);
-
-	return true;
-}
-
 /* ----------------
  *		printtup_shutdown
  * ----------------
@@ -638,88 +483,3 @@ debugtup(TupleTableSlot *slot, DestReceiver *self)
 
 	return true;
 }
-
-/* ----------------
- *		printtup_internal_20 --- print a binary tuple in protocol 2.0
- *
- * We use a different message type, i.e. 'B' instead of 'D' to
- * indicate a tuple in internal (binary) form.
- *
- * This is largely same as printtup_20, except we use binary formatting.
- * ----------------
- */
-static bool
-printtup_internal_20(TupleTableSlot *slot, DestReceiver *self)
-{
-	TupleDesc	typeinfo = slot->tts_tupleDescriptor;
-	DR_printtup *myState = (DR_printtup *) self;
-	MemoryContext oldcontext;
-	StringInfo	buf = &myState->buf;
-	int			natts = typeinfo->natts;
-	int			i,
-				j,
-				k;
-
-	/* Set or update my derived attribute info, if needed */
-	if (myState->attrinfo != typeinfo || myState->nattrs != natts)
-		printtup_prepare_info(myState, typeinfo, natts);
-
-	/* Make sure the tuple is fully deconstructed */
-	slot_getallattrs(slot);
-
-	/* Switch into per-row context so we can recover memory below */
-	oldcontext = MemoryContextSwitchTo(myState->tmpcontext);
-
-	/*
-	 * tell the frontend to expect new tuple data (in binary style)
-	 */
-	pq_beginmessage_reuse(buf, 'B');
-
-	/*
-	 * send a bitmap of which attributes are not null
-	 */
-	j = 0;
-	k = 1 << 7;
-	for (i = 0; i < natts; ++i)
-	{
-		if (!slot->tts_isnull[i])
-			j |= k;				/* set bit if not null */
-		k >>= 1;
-		if (k == 0)				/* end of byte? */
-		{
-			pq_sendint8(buf, j);
-			j = 0;
-			k = 1 << 7;
-		}
-	}
-	if (k != (1 << 7))			/* flush last partial byte */
-		pq_sendint8(buf, j);
-
-	/*
-	 * send the attributes of this tuple
-	 */
-	for (i = 0; i < natts; ++i)
-	{
-		PrinttupAttrInfo *thisState = myState->myinfo + i;
-		Datum		attr = slot->tts_values[i];
-		bytea	   *outputbytes;
-
-		if (slot->tts_isnull[i])
-			continue;
-
-		Assert(thisState->format == 1);
-
-		outputbytes = SendFunctionCall(&thisState->finfo, attr);
-		pq_sendint32(buf, VARSIZE(outputbytes) - VARHDRSZ);
-		pq_sendbytes(buf, VARDATA(outputbytes),
-					 VARSIZE(outputbytes) - VARHDRSZ);
-	}
-
-	pq_endmessage_reuse(buf);
-
-	/* Return to caller's context, and flush row's temporary memory */
-	MemoryContextSwitchTo(oldcontext);
-	MemoryContextReset(myState->tmpcontext);
-
-	return true;
-}
diff --git a/src/backend/commands/async.c b/src/backend/commands/async.c
index 42b232d98b..4b16fb5682 100644
--- a/src/backend/commands/async.c
+++ b/src/backend/commands/async.c
@@ -2311,8 +2311,7 @@ NotifyMyFrontEnd(const char *channel, const char *payload, int32 srcPid)
 		pq_beginmessage(&buf, 'A');
 		pq_sendint32(&buf, srcPid);
 		pq_sendstring(&buf, channel);
-		if (PG_PROTOCOL_MAJOR(FrontendProtocol) >= 3)
-			pq_sendstring(&buf, payload);
+		pq_sendstring(&buf, payload);
 		pq_endmessage(&buf);
 
 		/*
diff --git a/src/backend/commands/copyfrom.c b/src/backend/commands/copyfrom.c
index 796ca7b3f7..f05e2d2347 100644
--- a/src/backend/commands/copyfrom.c
+++ b/src/backend/commands/copyfrom.c
@@ -1126,13 +1126,6 @@ CopyFrom(CopyFromState cstate)
 
 	MemoryContextSwitchTo(oldcontext);
 
-	/*
-	 * In the old protocol, tell pqcomm that we can process normal protocol
-	 * messages again.
-	 */
-	if (cstate->copy_src == COPY_OLD_FE)
-		pq_endmsgread();
-
 	/* Execute AFTER STATEMENT insertion triggers */
 	ExecASInsertTriggers(estate, target_resultRelInfo, cstate->transition_capture);
 
diff --git a/src/backend/commands/copyfromparse.c b/src/backend/commands/copyfromparse.c
index 315b16fd7a..ce24a1528b 100644
--- a/src/backend/commands/copyfromparse.c
+++ b/src/backend/commands/copyfromparse.c
@@ -124,35 +124,19 @@ static int	CopyReadBinaryData(CopyFromState cstate, char *dest, int nbytes);
 void
 ReceiveCopyBegin(CopyFromState cstate)
 {
-	if (PG_PROTOCOL_MAJOR(FrontendProtocol) >= 3)
-	{
-		/* new way */
-		StringInfoData buf;
-		int			natts = list_length(cstate->attnumlist);
-		int16		format = (cstate->opts.binary ? 1 : 0);
-		int			i;
-
-		pq_beginmessage(&buf, 'G');
-		pq_sendbyte(&buf, format);	/* overall format */
-		pq_sendint16(&buf, natts);
-		for (i = 0; i < natts; i++)
-			pq_sendint16(&buf, format); /* per-column formats */
-		pq_endmessage(&buf);
-		cstate->copy_src = COPY_NEW_FE;
-		cstate->fe_msgbuf = makeStringInfo();
-	}
-	else
-	{
-		/* old way */
-		if (cstate->opts.binary)
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("COPY BINARY is not supported to stdout or from stdin")));
-		pq_putemptymessage('G');
-		/* any error in old protocol will make us lose sync */
-		pq_startmsgread();
-		cstate->copy_src = COPY_OLD_FE;
-	}
+	StringInfoData buf;
+	int			natts = list_length(cstate->attnumlist);
+	int16		format = (cstate->opts.binary ? 1 : 0);
+	int			i;
+
+	pq_beginmessage(&buf, 'G');
+	pq_sendbyte(&buf, format);	/* overall format */
+	pq_sendint16(&buf, natts);
+	for (i = 0; i < natts; i++)
+		pq_sendint16(&buf, format); /* per-column formats */
+	pq_endmessage(&buf);
+	cstate->copy_src = COPY_FRONTEND;
+	cstate->fe_msgbuf = makeStringInfo();
 	/* We *must* flush here to ensure FE knows it can send. */
 	pq_flush();
 }
@@ -228,25 +212,7 @@ CopyGetData(CopyFromState cstate, void *databuf, int minread, int maxread)
 			if (bytesread == 0)
 				cstate->reached_eof = true;
 			break;
-		case COPY_OLD_FE:
-
-			/*
-			 * We cannot read more than minread bytes (which in practice is 1)
-			 * because old protocol doesn't have any clear way of separating
-			 * the COPY stream from following data.  This is slow, but not any
-			 * slower than the code path was originally, and we don't care
-			 * much anymore about the performance of old protocol.
-			 */
-			if (pq_getbytes((char *) databuf, minread))
-			{
-				/* Only a \. terminator is legal EOF in old protocol */
-				ereport(ERROR,
-						(errcode(ERRCODE_CONNECTION_FAILURE),
-						 errmsg("unexpected EOF on client connection with an open transaction")));
-			}
-			bytesread = minread;
-			break;
-		case COPY_NEW_FE:
+		case COPY_FRONTEND:
 			while (maxread > 0 && bytesread < minread && !cstate->reached_eof)
 			{
 				int			avail;
@@ -619,21 +585,16 @@ NextCopyFrom(CopyFromState cstate, ExprContext *econtext,
 		if (fld_count == -1)
 		{
 			/*
-			 * Received EOF marker.  In a V3-protocol copy, wait for the
-			 * protocol-level EOF, and complain if it doesn't come
-			 * immediately.  This ensures that we correctly handle CopyFail,
-			 * if client chooses to send that now.
-			 *
-			 * Note that we MUST NOT try to read more data in an old-protocol
-			 * copy, since there is no protocol-level EOF marker then.  We
-			 * could go either way for copy from file, but choose to throw
-			 * error if there's data after the EOF marker, for consistency
-			 * with the new-protocol case.
+			 * Received EOF marker.  Wait for the protocol-level EOF, and
+			 * complain if it doesn't come immediately.  In COPY FROM STDIN,
+			 * this ensures that we correctly handle CopyFail, if client
+			 * chooses to send that now.  When copying from file, we could
+			 * ignore the rest of the file like in text mode, but we choose to
+			 * be consistent with the COPY FROM STDIN case.
 			 */
 			char		dummy;
 
-			if (cstate->copy_src != COPY_OLD_FE &&
-				CopyReadBinaryData(cstate, &dummy, 1) > 0)
+			if (CopyReadBinaryData(cstate, &dummy, 1) > 0)
 				ereport(ERROR,
 						(errcode(ERRCODE_BAD_COPY_FILE_FORMAT),
 						 errmsg("received copy data after EOF marker")));
@@ -712,7 +673,7 @@ CopyReadLine(CopyFromState cstate)
 		 * after \. up to the protocol end of copy data.  (XXX maybe better
 		 * not to treat \. as special?)
 		 */
-		if (cstate->copy_src == COPY_NEW_FE)
+		if (cstate->copy_src == COPY_FRONTEND)
 		{
 			do
 			{
diff --git a/src/backend/commands/copyto.c b/src/backend/commands/copyto.c
index e04ec1e331..46155015cf 100644
--- a/src/backend/commands/copyto.c
+++ b/src/backend/commands/copyto.c
@@ -50,8 +50,7 @@
 typedef enum CopyDest
 {
 	COPY_FILE,					/* to file (or a piped program) */
-	COPY_OLD_FE,				/* to frontend (2.0 protocol) */
-	COPY_NEW_FE,				/* to frontend (3.0 protocol) */
+	COPY_FRONTEND,				/* to frontend */
 } CopyDest;
 
 /*
@@ -116,7 +115,6 @@ static const char BinarySignature[11] = "PGCOPY\n\377\r\n\0";
 /* non-export function prototypes */
 static void EndCopy(CopyToState cstate);
 static void ClosePipeToProgram(CopyToState cstate);
-static uint64 CopyTo(CopyToState cstate);
 static void CopyOneRowTo(CopyToState cstate, TupleTableSlot *slot);
 static void CopyAttributeOutText(CopyToState cstate, char *string);
 static void CopyAttributeOutCSV(CopyToState cstate, char *string,
@@ -140,53 +138,27 @@ static void CopySendInt16(CopyToState cstate, int16 val);
 static void
 SendCopyBegin(CopyToState cstate)
 {
-	if (PG_PROTOCOL_MAJOR(FrontendProtocol) >= 3)
-	{
-		/* new way */
-		StringInfoData buf;
-		int			natts = list_length(cstate->attnumlist);
-		int16		format = (cstate->opts.binary ? 1 : 0);
-		int			i;
-
-		pq_beginmessage(&buf, 'H');
-		pq_sendbyte(&buf, format);	/* overall format */
-		pq_sendint16(&buf, natts);
-		for (i = 0; i < natts; i++)
-			pq_sendint16(&buf, format); /* per-column formats */
-		pq_endmessage(&buf);
-		cstate->copy_dest = COPY_NEW_FE;
-	}
-	else
-	{
-		/* old way */
-		if (cstate->opts.binary)
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("COPY BINARY is not supported to stdout or from stdin")));
-		pq_putemptymessage('H');
-		/* grottiness needed for old COPY OUT protocol */
-		pq_startcopyout();
-		cstate->copy_dest = COPY_OLD_FE;
-	}
+	StringInfoData buf;
+	int			natts = list_length(cstate->attnumlist);
+	int16		format = (cstate->opts.binary ? 1 : 0);
+	int			i;
+
+	pq_beginmessage(&buf, 'H');
+	pq_sendbyte(&buf, format);	/* overall format */
+	pq_sendint16(&buf, natts);
+	for (i = 0; i < natts; i++)
+		pq_sendint16(&buf, format); /* per-column formats */
+	pq_endmessage(&buf);
+	cstate->copy_dest = COPY_FRONTEND;
 }
 
 static void
 SendCopyEnd(CopyToState cstate)
 {
-	if (cstate->copy_dest == COPY_NEW_FE)
-	{
-		/* Shouldn't have any unsent data */
-		Assert(cstate->fe_msgbuf->len == 0);
-		/* Send Copy Done message */
-		pq_putemptymessage('c');
-	}
-	else
-	{
-		CopySendData(cstate, "\\.", 2);
-		/* Need to flush out the trailer (this also appends a newline) */
-		CopySendEndOfRow(cstate);
-		pq_endcopyout(false);
-	}
+	/* Shouldn't have any unsent data */
+	Assert(cstate->fe_msgbuf->len == 0);
+	/* Send Copy Done message */
+	pq_putemptymessage('c');
 }
 
 /*----------
@@ -268,20 +240,7 @@ CopySendEndOfRow(CopyToState cstate)
 							 errmsg("could not write to COPY file: %m")));
 			}
 			break;
-		case COPY_OLD_FE:
-			/* The FE/BE protocol uses \n as newline for all platforms */
-			if (!cstate->opts.binary)
-				CopySendChar(cstate, '\n');
-
-			if (pq_putbytes(fe_msgbuf->data, fe_msgbuf->len))
-			{
-				/* no hope of recovering connection sync, so FATAL */
-				ereport(FATAL,
-						(errcode(ERRCODE_CONNECTION_FAILURE),
-						 errmsg("connection lost during COPY to stdout")));
-			}
-			break;
-		case COPY_NEW_FE:
+		case COPY_FRONTEND:
 			/* The FE/BE protocol uses \n as newline for all platforms */
 			if (!cstate->opts.binary)
 				CopySendChar(cstate, '\n');
@@ -779,42 +738,6 @@ BeginCopyTo(ParseState *pstate,
 	return cstate;
 }
 
-/*
- * This intermediate routine exists mainly to localize the effects of setjmp
- * so we don't need to plaster a lot of variables with "volatile".
- */
-uint64
-DoCopyTo(CopyToState cstate)
-{
-	bool		pipe = (cstate->filename == NULL);
-	bool		fe_copy = (pipe && whereToSendOutput == DestRemote);
-	uint64		processed;
-
-	PG_TRY();
-	{
-		if (fe_copy)
-			SendCopyBegin(cstate);
-
-		processed = CopyTo(cstate);
-
-		if (fe_copy)
-			SendCopyEnd(cstate);
-	}
-	PG_CATCH();
-	{
-		/*
-		 * Make sure we turn off old-style COPY OUT mode upon error. It is
-		 * okay to do this in all cases, since it does nothing if the mode is
-		 * not on.
-		 */
-		pq_endcopyout(true);
-		PG_RE_THROW();
-	}
-	PG_END_TRY();
-
-	return processed;
-}
-
 /*
  * Clean up storage and release resources for COPY TO.
  */
@@ -837,14 +760,19 @@ EndCopyTo(CopyToState cstate)
 /*
  * Copy from relation or query TO file.
  */
-static uint64
-CopyTo(CopyToState cstate)
+uint64
+DoCopyTo(CopyToState cstate)
 {
+	bool		pipe = (cstate->filename == NULL);
+	bool		fe_copy = (pipe && whereToSendOutput == DestRemote);
 	TupleDesc	tupDesc;
 	int			num_phys_attrs;
 	ListCell   *cur;
 	uint64		processed;
 
+	if (fe_copy)
+		SendCopyBegin(cstate);
+
 	if (cstate->rel)
 		tupDesc = RelationGetDescr(cstate->rel);
 	else
@@ -977,11 +905,14 @@ CopyTo(CopyToState cstate)
 
 	MemoryContextDelete(cstate->rowcontext);
 
+	if (fe_copy)
+		SendCopyEnd(cstate);
+
 	return processed;
 }
 
 /*
- * Emit one row during CopyTo().
+ * Emit one row during DoCopyTo().
  */
 static void
 CopyOneRowTo(CopyToState cstate, TupleTableSlot *slot)
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index baa0712c0f..994251e7d9 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -653,35 +653,26 @@ static char *
 recv_password_packet(Port *port)
 {
 	StringInfoData buf;
+	int			mtype;
 
 	pq_startmsgread();
-	if (PG_PROTOCOL_MAJOR(port->proto) >= 3)
-	{
-		/* Expect 'p' message type */
-		int			mtype;
 
-		mtype = pq_getbyte();
-		if (mtype != 'p')
-		{
-			/*
-			 * If the client just disconnects without offering a password,
-			 * don't make a log entry.  This is legal per protocol spec and in
-			 * fact commonly done by psql, so complaining just clutters the
-			 * log.
-			 */
-			if (mtype != EOF)
-				ereport(ERROR,
-						(errcode(ERRCODE_PROTOCOL_VIOLATION),
-						 errmsg("expected password response, got message type %d",
-								mtype)));
-			return NULL;		/* EOF or bad message type */
-		}
-	}
-	else
+	/* Expect 'p' message type */
+	mtype = pq_getbyte();
+	if (mtype != 'p')
 	{
-		/* For pre-3.0 clients, avoid log entry if they just disconnect */
-		if (pq_peekbyte() == EOF)
-			return NULL;		/* EOF */
+		/*
+		 * If the client just disconnects without offering a password,
+		 * don't make a log entry.  This is legal per protocol spec and in
+		 * fact commonly done by psql, so complaining just clutters the
+		 * log.
+		 */
+		if (mtype != EOF)
+			ereport(ERROR,
+					(errcode(ERRCODE_PROTOCOL_VIOLATION),
+					 errmsg("expected password response, got message type %d",
+							mtype)));
+		return NULL;		/* EOF or bad message type */
 	}
 
 	initStringInfo(&buf);
@@ -879,19 +870,6 @@ CheckSCRAMAuth(Port *port, char *shadow_pass, char **logdetail)
 	int			result;
 	bool		initial;
 
-	/*
-	 * SASL auth is not supported for protocol versions before 3, because it
-	 * relies on the overall message length word to determine the SASL payload
-	 * size in AuthenticationSASLContinue and PasswordMessage messages.  (We
-	 * used to have a hard rule that protocol messages must be parsable
-	 * without relying on the length word, but we hardly care about older
-	 * protocol version anymore.)
-	 */
-	if (PG_PROTOCOL_MAJOR(FrontendProtocol) < 3)
-		ereport(FATAL,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("SASL authentication is not supported in protocol version 2")));
-
 	/*
 	 * Send the SASL authentication request to user.  It includes the list of
 	 * authentication mechanisms that are supported.
@@ -1041,19 +1019,6 @@ pg_GSS_recvauth(Port *port)
 	StringInfoData buf;
 	gss_buffer_desc gbuf;
 
-	/*
-	 * GSS auth is not supported for protocol versions before 3, because it
-	 * relies on the overall message length word to determine the GSS payload
-	 * size in AuthenticationGSSContinue and PasswordMessage messages. (This
-	 * is, in fact, a design error in our GSS support, because protocol
-	 * messages are supposed to be parsable without relying on the length
-	 * word; but it's not worth changing it now.)
-	 */
-	if (PG_PROTOCOL_MAJOR(FrontendProtocol) < 3)
-		ereport(FATAL,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("GSSAPI is not supported in protocol version 2")));
-
 	/*
 	 * Use the configured keytab, if there is one.  Unfortunately, Heimdal
 	 * doesn't support the cred store extensions, so use the env var.
@@ -1323,19 +1288,6 @@ pg_SSPI_recvauth(Port *port)
 
 	QUERY_SECURITY_CONTEXT_TOKEN_FN _QuerySecurityContextToken;
 
-	/*
-	 * SSPI auth is not supported for protocol versions before 3, because it
-	 * relies on the overall message length word to determine the SSPI payload
-	 * size in AuthenticationGSSContinue and PasswordMessage messages. (This
-	 * is, in fact, a design error in our SSPI support, because protocol
-	 * messages are supposed to be parsable without relying on the length
-	 * word; but it's not worth changing it now.)
-	 */
-	if (PG_PROTOCOL_MAJOR(FrontendProtocol) < 3)
-		ereport(FATAL,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("SSPI is not supported in protocol version 2")));
-
 	/*
 	 * Acquire a handle to the server credentials.
 	 */
diff --git a/src/backend/libpq/pqcomm.c b/src/backend/libpq/pqcomm.c
index 27a298f110..4c7b1e7bfd 100644
--- a/src/backend/libpq/pqcomm.c
+++ b/src/backend/libpq/pqcomm.c
@@ -5,23 +5,13 @@
  *
  * These routines handle the low-level details of communication between
  * frontend and backend.  They just shove data across the communication
- * channel, and are ignorant of the semantics of the data --- or would be,
- * except for major brain damage in the design of the old COPY OUT protocol.
- * Unfortunately, COPY OUT was designed to commandeer the communication
- * channel (it just transfers data without wrapping it into messages).
- * No other messages can be sent while COPY OUT is in progress; and if the
- * copy is aborted by an ereport(ERROR), we need to close out the copy so that
- * the frontend gets back into sync.  Therefore, these routines have to be
- * aware of COPY OUT state.  (New COPY-OUT is message-based and does *not*
- * set the DoingCopyOut flag.)
+ * channel, and are ignorant of the semantics of the data.
  *
- * NOTE: generally, it's a bad idea to emit outgoing messages directly with
- * pq_putbytes(), especially if the message would require multiple calls
- * to send.  Instead, use the routines in pqformat.c to construct the message
- * in a buffer and then emit it in one call to pq_putmessage.  This ensures
- * that the channel will not be clogged by an incomplete message if execution
- * is aborted by ereport(ERROR) partway through the message.  The only
- * non-libpq code that should call pq_putbytes directly is old-style COPY OUT.
+ * To emit an outgoing message, use the routines in pqformat.c to construct
+ * the message in a buffer and then emit it in one call to pq_putmessage.
+ * There are no functions to send raw bytes or partial messages; this
+ * ensures that the channel will not be clogged by an incomplete message if
+ * execution is aborted by ereport(ERROR) partway through the message.
  *
  * At one time, libpq was shared between frontend and backend, but now
  * the backend's "backend/libpq" is quite separate from "interfaces/libpq".
@@ -49,20 +39,16 @@
  *
  * low-level I/O:
  *		pq_getbytes		- get a known number of bytes from connection
- *		pq_getstring	- get a null terminated string from connection
  *		pq_getmessage	- get a message with length word from connection
  *		pq_getbyte		- get next byte from connection
  *		pq_peekbyte		- peek at next byte from connection
- *		pq_putbytes		- send bytes to connection (not flushed until pq_flush)
  *		pq_flush		- flush pending output
  *		pq_flush_if_writable - flush pending output if writable without blocking
  *		pq_getbyte_if_available - get a byte if available without blocking
  *
- * message-level I/O (and old-style-COPY-OUT cruft):
+ * message-level I/O
  *		pq_putmessage	- send a normal message (suppressed in COPY OUT mode)
  *		pq_putmessage_noblock - buffer a normal message (suppressed in COPY OUT)
- *		pq_startcopyout - inform libpq that a COPY OUT transfer is beginning
- *		pq_endcopyout	- end a COPY OUT transfer
  *
  *------------------------
  */
@@ -146,7 +132,6 @@ static int	PqRecvLength;		/* End of data available in PqRecvBuffer */
  */
 static bool PqCommBusy;			/* busy sending data to the client */
 static bool PqCommReadingMsg;	/* in the middle of reading a message */
-static bool DoingCopyOut;		/* in old-protocol COPY OUT processing */
 
 
 /* Internal functions */
@@ -158,8 +143,6 @@ static int	socket_flush_if_writable(void);
 static bool socket_is_send_pending(void);
 static int	socket_putmessage(char msgtype, const char *s, size_t len);
 static void socket_putmessage_noblock(char msgtype, const char *s, size_t len);
-static void socket_startcopyout(void);
-static void socket_endcopyout(bool errorAbort);
 static int	internal_putbytes(const char *s, size_t len);
 static int	internal_flush(void);
 
@@ -174,9 +157,7 @@ static const PQcommMethods PqCommSocketMethods = {
 	socket_flush_if_writable,
 	socket_is_send_pending,
 	socket_putmessage,
-	socket_putmessage_noblock,
-	socket_startcopyout,
-	socket_endcopyout
+	socket_putmessage_noblock
 };
 
 const PQcommMethods *PqCommMethods = &PqCommSocketMethods;
@@ -200,7 +181,6 @@ pq_init(void)
 	PqSendPointer = PqSendStart = PqRecvPointer = PqRecvLength = 0;
 	PqCommBusy = false;
 	PqCommReadingMsg = false;
-	DoingCopyOut = false;
 
 	/* set up process-exit hook to close the socket */
 	on_proc_exit(socket_close, 0);
@@ -250,8 +230,6 @@ socket_comm_reset(void)
 {
 	/* Do not throw away pending data, but do reset the busy flag */
 	PqCommBusy = false;
-	/* We can abort any old-style COPY OUT, too */
-	pq_endcopyout(true);
 }
 
 /* --------------------------------
@@ -1158,58 +1136,6 @@ pq_discardbytes(size_t len)
 	return 0;
 }
 
-/* --------------------------------
- *		pq_getstring	- get a null terminated string from connection
- *
- *		The return value is placed in an expansible StringInfo, which has
- *		already been initialized by the caller.
- *
- *		This is used only for dealing with old-protocol clients.  The idea
- *		is to produce a StringInfo that looks the same as we would get from
- *		pq_getmessage() with a newer client; we will then process it with
- *		pq_getmsgstring.  Therefore, no character set conversion is done here,
- *		even though this is presumably useful only for text.
- *
- *		returns 0 if OK, EOF if trouble
- * --------------------------------
- */
-int
-pq_getstring(StringInfo s)
-{
-	int			i;
-
-	Assert(PqCommReadingMsg);
-
-	resetStringInfo(s);
-
-	/* Read until we get the terminating '\0' */
-	for (;;)
-	{
-		while (PqRecvPointer >= PqRecvLength)
-		{
-			if (pq_recvbuf())	/* If nothing in buffer, then recv some */
-				return EOF;		/* Failed to recv data */
-		}
-
-		for (i = PqRecvPointer; i < PqRecvLength; i++)
-		{
-			if (PqRecvBuffer[i] == '\0')
-			{
-				/* include the '\0' in the copy */
-				appendBinaryStringInfo(s, PqRecvBuffer + PqRecvPointer,
-									   i - PqRecvPointer + 1);
-				PqRecvPointer = i + 1;	/* advance past \0 */
-				return 0;
-			}
-		}
-
-		/* If we're here we haven't got the \0 in the buffer yet. */
-		appendBinaryStringInfo(s, PqRecvBuffer + PqRecvPointer,
-							   PqRecvLength - PqRecvPointer);
-		PqRecvPointer = PqRecvLength;
-	}
-}
-
 
 /* --------------------------------
  *		pq_startmsgread - begin reading a message from the client.
@@ -1236,9 +1162,9 @@ pq_startmsgread(void)
 /* --------------------------------
  *		pq_endmsgread	- finish reading message.
  *
- *		This must be called after reading a V2 protocol message with
- *		pq_getstring() and friends, to indicate that we have read the whole
- *		message. In V3 protocol, pq_getmessage() does this implicitly.
+ *		This must be called after reading a message with pq_getbytes()
+ *		and friends, to indicate that we have read the whole message.
+ *		pq_getmessage() does this implicitly.
  * --------------------------------
  */
 void
@@ -1354,28 +1280,6 @@ pq_getmessage(StringInfo s, int maxlen)
 }
 
 
-/* --------------------------------
- *		pq_putbytes		- send bytes to connection (not flushed until pq_flush)
- *
- *		returns 0 if OK, EOF if trouble
- * --------------------------------
- */
-int
-pq_putbytes(const char *s, size_t len)
-{
-	int			res;
-
-	/* Should only be called by old-style COPY OUT */
-	Assert(DoingCopyOut);
-	/* No-op if reentrant call */
-	if (PqCommBusy)
-		return 0;
-	PqCommBusy = true;
-	res = internal_putbytes(s, len);
-	PqCommBusy = false;
-	return res;
-}
-
 static int
 internal_putbytes(const char *s, size_t len)
 {
@@ -1536,8 +1440,6 @@ socket_is_send_pending(void)
 
 /* --------------------------------
  * Message-level I/O routines begin here.
- *
- * These routines understand about the old-style COPY OUT protocol.
  * --------------------------------
  */
 
@@ -1545,20 +1447,13 @@ socket_is_send_pending(void)
 /* --------------------------------
  *		socket_putmessage - send a normal message (suppressed in COPY OUT mode)
  *
- *		If msgtype is not '\0', it is a message type code to place before
- *		the message body.  If msgtype is '\0', then the message has no type
- *		code (this is only valid in pre-3.0 protocols).
- *
- *		len is the length of the message body data at *s.  In protocol 3.0
- *		and later, a message length word (equal to len+4 because it counts
- *		itself too) is inserted by this routine.
+ *		msgtype is a message type code to place before the message body.
  *
- *		All normal messages are suppressed while old-style COPY OUT is in
- *		progress.  (In practice only a few notice messages might get emitted
- *		then; dropping them is annoying, but at least they will still appear
- *		in the postmaster log.)
+ *		len is the length of the message body data at *s.  A message length
+ *		word (equal to len+4 because it counts itself too) is inserted by this
+ *		routine.
  *
- *		We also suppress messages generated while pqcomm.c is busy.  This
+ *		We suppress messages generated while pqcomm.c is busy.  This
  *		avoids any possibility of messages being inserted within other
  *		messages.  The only known trouble case arises if SIGQUIT occurs
  *		during a pqcomm.c routine --- quickdie() will try to send a warning
@@ -1570,20 +1465,20 @@ socket_is_send_pending(void)
 static int
 socket_putmessage(char msgtype, const char *s, size_t len)
 {
-	if (DoingCopyOut || PqCommBusy)
+	uint32		n32;
+
+	Assert(msgtype != 0);
+
+	if (PqCommBusy)
 		return 0;
 	PqCommBusy = true;
-	if (msgtype)
-		if (internal_putbytes(&msgtype, 1))
-			goto fail;
-	if (PG_PROTOCOL_MAJOR(FrontendProtocol) >= 3)
-	{
-		uint32		n32;
+	if (internal_putbytes(&msgtype, 1))
+		goto fail;
+
+	n32 = pg_hton32((uint32) (len + 4));
+	if (internal_putbytes((char *) &n32, 4))
+		goto fail;
 
-		n32 = pg_hton32((uint32) (len + 4));
-		if (internal_putbytes((char *) &n32, 4))
-			goto fail;
-	}
 	if (internal_putbytes(s, len))
 		goto fail;
 	PqCommBusy = false;
@@ -1621,37 +1516,41 @@ socket_putmessage_noblock(char msgtype, const char *s, size_t len)
 								 * buffer */
 }
 
-
-/* --------------------------------
- *		socket_startcopyout - inform libpq that an old-style COPY OUT transfer
- *			is beginning
- * --------------------------------
- */
-static void
-socket_startcopyout(void)
-{
-	DoingCopyOut = true;
-}
-
 /* --------------------------------
- *		socket_endcopyout	- end an old-style COPY OUT transfer
+ *		pq_putmessage_v2 - send a message in protocol version 2
+ *
+ *		msgtype is a message type code to place before the message body.
  *
- *		If errorAbort is indicated, we are aborting a COPY OUT due to an error,
- *		and must send a terminator line.  Since a partial data line might have
- *		been emitted, send a couple of newlines first (the first one could
- *		get absorbed by a backslash...)  Note that old-style COPY OUT does
- *		not allow binary transfers, so a textual terminator is always correct.
+ *		We no longer support protocol version 2, but we have kept this
+ *		function so that if a client tries to connect with protocol version 2,
+ *		as a courtesy we can still send the "unsupported protocol version"
+ *		error to the client in the old format.
+ *
+ *		Like in pq_putmessage(), we suppress messages generated while
+ *		pqcomm.c is busy.
+ *
+ *		returns 0 if OK, EOF if trouble
  * --------------------------------
  */
-static void
-socket_endcopyout(bool errorAbort)
+int
+pq_putmessage_v2(char msgtype, const char *s, size_t len)
 {
-	if (!DoingCopyOut)
-		return;
-	if (errorAbort)
-		pq_putbytes("\n\n\\.\n", 5);
-	/* in non-error case, copyto.c will have emitted the terminator line */
-	DoingCopyOut = false;
+	Assert(msgtype != 0);
+
+	if (PqCommBusy)
+		return 0;
+	PqCommBusy = true;
+	if (internal_putbytes(&msgtype, 1))
+		goto fail;
+
+	if (internal_putbytes(s, len))
+		goto fail;
+	PqCommBusy = false;
+	return 0;
+
+fail:
+	PqCommBusy = false;
+	return EOF;
 }
 
 /*
diff --git a/src/backend/libpq/pqmq.c b/src/backend/libpq/pqmq.c
index f468441b7a..d1a1f47a78 100644
--- a/src/backend/libpq/pqmq.c
+++ b/src/backend/libpq/pqmq.c
@@ -33,8 +33,6 @@ static int	mq_flush_if_writable(void);
 static bool mq_is_send_pending(void);
 static int	mq_putmessage(char msgtype, const char *s, size_t len);
 static void mq_putmessage_noblock(char msgtype, const char *s, size_t len);
-static void mq_startcopyout(void);
-static void mq_endcopyout(bool errorAbort);
 
 static const PQcommMethods PqCommMqMethods = {
 	mq_comm_reset,
@@ -42,9 +40,7 @@ static const PQcommMethods PqCommMqMethods = {
 	mq_flush_if_writable,
 	mq_is_send_pending,
 	mq_putmessage,
-	mq_putmessage_noblock,
-	mq_startcopyout,
-	mq_endcopyout
+	mq_putmessage_noblock
 };
 
 /*
@@ -195,18 +191,6 @@ mq_putmessage_noblock(char msgtype, const char *s, size_t len)
 	elog(ERROR, "not currently supported");
 }
 
-static void
-mq_startcopyout(void)
-{
-	/* Nothing to do. */
-}
-
-static void
-mq_endcopyout(bool errorAbort)
-{
-	/* Nothing to do. */
-}
-
 /*
  * Parse an ErrorResponse or NoticeResponse payload and populate an ErrorData
  * structure with the results.
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index 3f1ce135a8..edab95a19e 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -1934,7 +1934,7 @@ static int
 ProcessStartupPacket(Port *port, bool ssl_done, bool gss_done)
 {
 	int32		len;
-	void	   *buf;
+	char	   *buf;
 	ProtocolVersion proto;
 	MemoryContext oldcontext;
 
@@ -1984,15 +1984,12 @@ ProcessStartupPacket(Port *port, bool ssl_done, bool gss_done)
 	}
 
 	/*
-	 * Allocate at least the size of an old-style startup packet, plus one
-	 * extra byte, and make sure all are zeroes.  This ensures we will have
-	 * null termination of all strings, in both fixed- and variable-length
-	 * packet layouts.
+	 * Allocate space to hold the startup packet, plus one extra byte that's
+	 * initialized to be zero.  This ensures we will have null termination of
+	 * all strings inside the packet.
 	 */
-	if (len <= (int32) sizeof(StartupPacket))
-		buf = palloc0(sizeof(StartupPacket) + 1);
-	else
-		buf = palloc0(len + 1);
+	buf = palloc(len + 1);
+	buf[len] = '\0';
 
 	if (pq_getbytes(buf, len) == EOF)
 	{
@@ -2115,7 +2112,7 @@ retry1:
 	 */
 	oldcontext = MemoryContextSwitchTo(TopMemoryContext);
 
-	if (PG_PROTOCOL_MAJOR(proto) >= 3)
+	/* Handle protocol version 3 startup packet */
 	{
 		int32		offset = sizeof(ProtocolVersion);
 		List	   *unrecognized_protocol_options = NIL;
@@ -2129,7 +2126,7 @@ retry1:
 
 		while (offset < len)
 		{
-			char	   *nameptr = ((char *) buf) + offset;
+			char	   *nameptr = buf + offset;
 			int32		valoffset;
 			char	   *valptr;
 
@@ -2138,7 +2135,7 @@ retry1:
 			valoffset = offset + strlen(nameptr) + 1;
 			if (valoffset >= len)
 				break;			/* missing value, will complain below */
-			valptr = ((char *) buf) + valoffset;
+			valptr = buf + valoffset;
 
 			if (strcmp(nameptr, "database") == 0)
 				port->database_name = pstrdup(valptr);
@@ -2223,27 +2220,6 @@ retry1:
 			unrecognized_protocol_options != NIL)
 			SendNegotiateProtocolVersion(unrecognized_protocol_options);
 	}
-	else
-	{
-		/*
-		 * Get the parameters from the old-style, fixed-width-fields startup
-		 * packet as C strings.  The packet destination was cleared first so a
-		 * short packet has zeros silently added.  We have to be prepared to
-		 * truncate the pstrdup result for oversize fields, though.
-		 */
-		StartupPacket *packet = (StartupPacket *) buf;
-
-		port->database_name = pstrdup(packet->database);
-		if (strlen(port->database_name) > sizeof(packet->database))
-			port->database_name[sizeof(packet->database)] = '\0';
-		port->user_name = pstrdup(packet->user);
-		if (strlen(port->user_name) > sizeof(packet->user))
-			port->user_name[sizeof(packet->user)] = '\0';
-		port->cmdline_options = pstrdup(packet->options);
-		if (strlen(port->cmdline_options) > sizeof(packet->options))
-			port->cmdline_options[sizeof(packet->options)] = '\0';
-		port->guc_options = NIL;
-	}
 
 	/* Check a user name was given. */
 	if (port->user_name == NULL || port->user_name[0] == '\0')
diff --git a/src/backend/tcop/dest.c b/src/backend/tcop/dest.c
index 4316137a9d..1dfadfa8e1 100644
--- a/src/backend/tcop/dest.c
+++ b/src/backend/tcop/dest.c
@@ -226,13 +226,8 @@ EndReplicationCommand(const char *commandTag)
 /* ----------------
  *		NullCommand - tell dest that an empty query string was recognized
  *
- *		In FE/BE protocol version 1.0, this hack is necessary to support
- *		libpq's crufty way of determining whether a multiple-command
- *		query string is done.  In protocol 2.0 it's probably not really
- *		necessary to distinguish empty queries anymore, but we still do it
- *		for backwards compatibility with 1.0.  In protocol 3.0 it has some
- *		use again, since it ensures that there will be a recognizable end
- *		to the response to an Execute message.
+ *		This ensures that there will be a recognizable end to the response
+ *		to an Execute message in the extended query protocol.
  * ----------------
  */
 void
@@ -244,14 +239,8 @@ NullCommand(CommandDest dest)
 		case DestRemoteExecute:
 		case DestRemoteSimple:
 
-			/*
-			 * tell the fe that we saw an empty query string.  In protocols
-			 * before 3.0 this has a useless empty-string message body.
-			 */
-			if (PG_PROTOCOL_MAJOR(FrontendProtocol) >= 3)
-				pq_putemptymessage('I');
-			else
-				pq_putmessage('I', "", 1);
+			/* Tell the FE that we saw an empty query string */
+			pq_putemptymessage('I');
 			break;
 
 		case DestNone:
@@ -286,7 +275,6 @@ ReadyForQuery(CommandDest dest)
 		case DestRemote:
 		case DestRemoteExecute:
 		case DestRemoteSimple:
-			if (PG_PROTOCOL_MAJOR(FrontendProtocol) >= 3)
 			{
 				StringInfoData buf;
 
@@ -294,8 +282,6 @@ ReadyForQuery(CommandDest dest)
 				pq_sendbyte(&buf, TransactionBlockStatusCode());
 				pq_endmessage(&buf);
 			}
-			else
-				pq_putemptymessage('Z');
 			/* Flush output at end of cycle in any case. */
 			pq_flush();
 			break;
diff --git a/src/backend/tcop/fastpath.c b/src/backend/tcop/fastpath.c
index 1b76653caa..77d17ebca9 100644
--- a/src/backend/tcop/fastpath.c
+++ b/src/backend/tcop/fastpath.c
@@ -58,98 +58,24 @@ struct fp_info
 
 static int16 parse_fcall_arguments(StringInfo msgBuf, struct fp_info *fip,
 								   FunctionCallInfo fcinfo);
-static int16 parse_fcall_arguments_20(StringInfo msgBuf, struct fp_info *fip,
-									  FunctionCallInfo fcinfo);
-
-
-/* ----------------
- *		GetOldFunctionMessage
- *
- * In pre-3.0 protocol, there is no length word on the message, so we have
- * to have code that understands the message layout to absorb the message
- * into a buffer.  We want to do this before we start execution, so that
- * we do not lose sync with the frontend if there's an error.
- *
- * The caller should already have initialized buf to empty.
- * ----------------
- */
-int
-GetOldFunctionMessage(StringInfo buf)
-{
-	int32		ibuf;
-	int			nargs;
-
-	/* Dummy string argument */
-	if (pq_getstring(buf))
-		return EOF;
-	/* Function OID */
-	if (pq_getbytes((char *) &ibuf, 4))
-		return EOF;
-	appendBinaryStringInfo(buf, (char *) &ibuf, 4);
-	/* Number of arguments */
-	if (pq_getbytes((char *) &ibuf, 4))
-		return EOF;
-	appendBinaryStringInfo(buf, (char *) &ibuf, 4);
-	nargs = pg_ntoh32(ibuf);
-	/* For each argument ... */
-	while (nargs-- > 0)
-	{
-		int			argsize;
-
-		/* argsize */
-		if (pq_getbytes((char *) &ibuf, 4))
-			return EOF;
-		appendBinaryStringInfo(buf, (char *) &ibuf, 4);
-		argsize = pg_ntoh32(ibuf);
-		if (argsize < -1)
-		{
-			/* FATAL here since no hope of regaining message sync */
-			ereport(FATAL,
-					(errcode(ERRCODE_PROTOCOL_VIOLATION),
-					 errmsg("invalid argument size %d in function call message",
-							argsize)));
-		}
-		/* and arg contents */
-		if (argsize > 0)
-		{
-			/* Allocate space for arg */
-			enlargeStringInfo(buf, argsize);
-			/* And grab it */
-			if (pq_getbytes(buf->data + buf->len, argsize))
-				return EOF;
-			buf->len += argsize;
-			/* Place a trailing null per StringInfo convention */
-			buf->data[buf->len] = '\0';
-		}
-	}
-	return 0;
-}
 
 /* ----------------
  *		SendFunctionResult
- *
- * Note: although this routine doesn't check, the format had better be 1
- * (binary) when talking to a pre-3.0 client.
  * ----------------
  */
 static void
 SendFunctionResult(Datum retval, bool isnull, Oid rettype, int16 format)
 {
-	bool		newstyle = (PG_PROTOCOL_MAJOR(FrontendProtocol) >= 3);
 	StringInfoData buf;
 
 	pq_beginmessage(&buf, 'V');
 
 	if (isnull)
 	{
-		if (newstyle)
-			pq_sendint32(&buf, -1);
+		pq_sendint32(&buf, -1);
 	}
 	else
 	{
-		if (!newstyle)
-			pq_sendbyte(&buf, 'G');
-
 		if (format == 0)
 		{
 			Oid			typoutput;
@@ -180,9 +106,6 @@ SendFunctionResult(Datum retval, bool isnull, Oid rettype, int16 format)
 					 errmsg("unsupported format code: %d", format)));
 	}
 
-	if (!newstyle)
-		pq_sendbyte(&buf, '0');
-
 	pq_endmessage(&buf);
 }
 
@@ -288,9 +211,6 @@ HandleFunctionRequest(StringInfo msgBuf)
 	/*
 	 * Begin parsing the buffer contents.
 	 */
-	if (PG_PROTOCOL_MAJOR(FrontendProtocol) < 3)
-		(void) pq_getmsgstring(msgBuf); /* dummy string */
-
 	fid = (Oid) pq_getmsgint(msgBuf, 4);	/* function oid */
 
 	/*
@@ -334,10 +254,7 @@ HandleFunctionRequest(StringInfo msgBuf)
 	 */
 	InitFunctionCallInfoData(*fcinfo, &fip->flinfo, 0, InvalidOid, NULL, NULL);
 
-	if (PG_PROTOCOL_MAJOR(FrontendProtocol) >= 3)
-		rformat = parse_fcall_arguments(msgBuf, fip, fcinfo);
-	else
-		rformat = parse_fcall_arguments_20(msgBuf, fip, fcinfo);
+	rformat = parse_fcall_arguments(msgBuf, fip, fcinfo);
 
 	/* Verify we reached the end of the message where expected. */
 	pq_getmsgend(msgBuf);
@@ -533,81 +450,3 @@ parse_fcall_arguments(StringInfo msgBuf, struct fp_info *fip,
 	/* Return result format code */
 	return (int16) pq_getmsgint(msgBuf, 2);
 }
-
-/*
- * Parse function arguments in a 2.0 protocol message
- *
- * Argument values are loaded into *fcinfo, and the desired result format
- * is returned.
- */
-static int16
-parse_fcall_arguments_20(StringInfo msgBuf, struct fp_info *fip,
-						 FunctionCallInfo fcinfo)
-{
-	int			nargs;
-	int			i;
-	StringInfoData abuf;
-
-	nargs = pq_getmsgint(msgBuf, 4);	/* # of arguments */
-
-	if (fip->flinfo.fn_nargs != nargs || nargs > FUNC_MAX_ARGS)
-		ereport(ERROR,
-				(errcode(ERRCODE_PROTOCOL_VIOLATION),
-				 errmsg("function call message contains %d arguments but function requires %d",
-						nargs, fip->flinfo.fn_nargs)));
-
-	fcinfo->nargs = nargs;
-
-	initStringInfo(&abuf);
-
-	/*
-	 * Copy supplied arguments into arg vector.  In protocol 2.0 these are
-	 * always assumed to be supplied in binary format.
-	 *
-	 * Note: although the original protocol 2.0 code did not have any way for
-	 * the frontend to specify a NULL argument, we now choose to interpret
-	 * length == -1 as meaning a NULL.
-	 */
-	for (i = 0; i < nargs; ++i)
-	{
-		int			argsize;
-		Oid			typreceive;
-		Oid			typioparam;
-
-		getTypeBinaryInputInfo(fip->argtypes[i], &typreceive, &typioparam);
-
-		argsize = pq_getmsgint(msgBuf, 4);
-		if (argsize == -1)
-		{
-			fcinfo->args[i].isnull = true;
-			fcinfo->args[i].value = OidReceiveFunctionCall(typreceive, NULL,
-														   typioparam, -1);
-			continue;
-		}
-		fcinfo->args[i].isnull = false;
-		if (argsize < 0)
-			ereport(ERROR,
-					(errcode(ERRCODE_PROTOCOL_VIOLATION),
-					 errmsg("invalid argument size %d in function call message",
-							argsize)));
-
-		/* Reset abuf to empty, and insert raw data into it */
-		resetStringInfo(&abuf);
-		appendBinaryStringInfo(&abuf,
-							   pq_getmsgbytes(msgBuf, argsize),
-							   argsize);
-
-		fcinfo->args[i].value = OidReceiveFunctionCall(typreceive, &abuf,
-													   typioparam, -1);
-
-		/* Trouble if it didn't eat the whole buffer */
-		if (abuf.cursor != abuf.len)
-			ereport(ERROR,
-					(errcode(ERRCODE_INVALID_BINARY_REPRESENTATION),
-					 errmsg("incorrect binary data format in function argument %d",
-							i + 1)));
-	}
-
-	/* Desired result format is always binary in protocol 2.0 */
-	return 1;
-}
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index bb5ccb4578..8a0332dde9 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -370,57 +370,10 @@ SocketBackend(StringInfo inBuf)
 	{
 		case 'Q':				/* simple query */
 			doing_extended_query_message = false;
-			if (PG_PROTOCOL_MAJOR(FrontendProtocol) < 3)
-			{
-				/* old style without length word; convert */
-				if (pq_getstring(inBuf))
-				{
-					if (IsTransactionState())
-						ereport(COMMERROR,
-								(errcode(ERRCODE_CONNECTION_FAILURE),
-								 errmsg("unexpected EOF on client connection with an open transaction")));
-					else
-					{
-						/*
-						 * Can't send DEBUG log messages to client at this
-						 * point. Since we're disconnecting right away, we
-						 * don't need to restore whereToSendOutput.
-						 */
-						whereToSendOutput = DestNone;
-						ereport(DEBUG1,
-								(errcode(ERRCODE_CONNECTION_DOES_NOT_EXIST),
-								 errmsg_internal("unexpected EOF on client connection")));
-					}
-					return EOF;
-				}
-			}
 			break;
 
 		case 'F':				/* fastpath function call */
 			doing_extended_query_message = false;
-			if (PG_PROTOCOL_MAJOR(FrontendProtocol) < 3)
-			{
-				if (GetOldFunctionMessage(inBuf))
-				{
-					if (IsTransactionState())
-						ereport(COMMERROR,
-								(errcode(ERRCODE_CONNECTION_FAILURE),
-								 errmsg("unexpected EOF on client connection with an open transaction")));
-					else
-					{
-						/*
-						 * Can't send DEBUG log messages to client at this
-						 * point. Since we're disconnecting right away, we
-						 * don't need to restore whereToSendOutput.
-						 */
-						whereToSendOutput = DestNone;
-						ereport(DEBUG1,
-								(errcode(ERRCODE_CONNECTION_DOES_NOT_EXIST),
-								 errmsg_internal("unexpected EOF on client connection")));
-					}
-					return EOF;
-				}
-			}
 			break;
 
 		case 'X':				/* terminate */
@@ -435,11 +388,6 @@ SocketBackend(StringInfo inBuf)
 		case 'H':				/* flush */
 		case 'P':				/* parse */
 			doing_extended_query_message = true;
-			/* these are only legal in protocol 3 */
-			if (PG_PROTOCOL_MAJOR(FrontendProtocol) < 3)
-				ereport(FATAL,
-						(errcode(ERRCODE_PROTOCOL_VIOLATION),
-						 errmsg("invalid frontend message type %d", qtype)));
 			break;
 
 		case 'S':				/* sync */
@@ -447,22 +395,12 @@ SocketBackend(StringInfo inBuf)
 			ignore_till_sync = false;
 			/* mark not-extended, so that a new error doesn't begin skip */
 			doing_extended_query_message = false;
-			/* only legal in protocol 3 */
-			if (PG_PROTOCOL_MAJOR(FrontendProtocol) < 3)
-				ereport(FATAL,
-						(errcode(ERRCODE_PROTOCOL_VIOLATION),
-						 errmsg("invalid frontend message type %d", qtype)));
 			break;
 
 		case 'd':				/* copy data */
 		case 'c':				/* copy done */
 		case 'f':				/* copy fail */
 			doing_extended_query_message = false;
-			/* these are only legal in protocol 3 */
-			if (PG_PROTOCOL_MAJOR(FrontendProtocol) < 3)
-				ereport(FATAL,
-						(errcode(ERRCODE_PROTOCOL_VIOLATION),
-						 errmsg("invalid frontend message type %d", qtype)));
 			break;
 
 		default:
@@ -483,13 +421,8 @@ SocketBackend(StringInfo inBuf)
 	 * after the type code; we can read the message contents independently of
 	 * the type.
 	 */
-	if (PG_PROTOCOL_MAJOR(FrontendProtocol) >= 3)
-	{
-		if (pq_getmessage(inBuf, 0))
-			return EOF;			/* suitable message already logged */
-	}
-	else
-		pq_endmsgread();
+	if (pq_getmessage(inBuf, 0))
+		return EOF;			/* suitable message already logged */
 	RESUME_CANCEL_INTERRUPTS();
 
 	return qtype;
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 80c2672461..e729ebece7 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -589,16 +589,6 @@ errfinish(const char *filename, int lineno, const char *funcname)
 		PG_RE_THROW();
 	}
 
-	/*
-	 * If we are doing FATAL or PANIC, abort any old-style COPY OUT in
-	 * progress, so that we can report the message before dying.  (Without
-	 * this, pq_putmessage will refuse to send the message at all, which is
-	 * what we want for NOTICE messages, but not for fatal exits.) This hack
-	 * is necessary because of poor design of old-style copy protocol.
-	 */
-	if (elevel >= FATAL && whereToSendOutput == DestRemote)
-		pq_endcopyout(true);
-
 	/* Emit the message to the right places */
 	EmitErrorReport();
 
@@ -1261,28 +1251,6 @@ errhidecontext(bool hide_ctx)
 	return 0;					/* return value does not matter */
 }
 
-
-/*
- * errfunction --- add reporting function name to the current error
- *
- * This is used when backwards compatibility demands that the function
- * name appear in messages sent to old-protocol clients.  Note that the
- * passed string is expected to be a non-freeable constant string.
- */
-int
-errfunction(const char *funcname)
-{
-	ErrorData  *edata = &errordata[errordata_stack_depth];
-
-	/* we don't bother incrementing recursion_depth */
-	CHECK_STACK_DEPTH();
-
-	edata->funcname = funcname;
-	edata->show_funcname = true;
-
-	return 0;					/* return value does not matter */
-}
-
 /*
  * errposition --- add cursor position to the current error
  */
@@ -3291,10 +3259,14 @@ send_message_to_frontend(ErrorData *edata)
 {
 	StringInfoData msgbuf;
 
-	/* 'N' (Notice) is for nonfatal conditions, 'E' is for errors */
-	pq_beginmessage(&msgbuf, (edata->elevel < ERROR) ? 'N' : 'E');
-
-	if (PG_PROTOCOL_MAJOR(FrontendProtocol) >= 3)
+	/*
+	 * We no longer support pre-3.0 FE/BE protocol, except here.  If a client
+	 * tries to connect using an older protocol version, it's nice to send the
+	 * "protocol version not supported" error in a format the client
+	 * understands.  If protocol hasn't been set yet, early in backend
+	 * startup, assume modern protocol.
+	 */
+	if (PG_PROTOCOL_MAJOR(FrontendProtocol) >= 3 || FrontendProtocol == 0)
 	{
 		/* New style with separate fields */
 		const char *sev;
@@ -3302,6 +3274,9 @@ send_message_to_frontend(ErrorData *edata)
 		int			ssval;
 		int			i;
 
+		/* 'N' (Notice) is for nonfatal conditions, 'E' is for errors */
+		pq_beginmessage(&msgbuf, (edata->elevel < ERROR) ? 'N' : 'E');
+
 		sev = error_severity(edata->elevel);
 		pq_sendbyte(&msgbuf, PG_DIAG_SEVERITY);
 		err_sendstring(&msgbuf, _(sev));
@@ -3417,6 +3392,8 @@ send_message_to_frontend(ErrorData *edata)
 		}
 
 		pq_sendbyte(&msgbuf, '\0'); /* terminator */
+
+		pq_endmessage(&msgbuf);
 	}
 	else
 	{
@@ -3427,30 +3404,19 @@ send_message_to_frontend(ErrorData *edata)
 
 		appendStringInfo(&buf, "%s:  ", _(error_severity(edata->elevel)));
 
-		if (edata->show_funcname && edata->funcname)
-			appendStringInfo(&buf, "%s: ", edata->funcname);
-
 		if (edata->message)
 			appendStringInfoString(&buf, edata->message);
 		else
 			appendStringInfoString(&buf, _("missing error text"));
 
-		if (edata->cursorpos > 0)
-			appendStringInfo(&buf, _(" at character %d"),
-							 edata->cursorpos);
-		else if (edata->internalpos > 0)
-			appendStringInfo(&buf, _(" at character %d"),
-							 edata->internalpos);
-
 		appendStringInfoChar(&buf, '\n');
 
-		err_sendstring(&msgbuf, buf.data);
+		/* 'N' (Notice) is for nonfatal conditions, 'E' is for errors */
+		pq_putmessage_v2((edata->elevel < ERROR) ? 'N' : 'E', buf.data, buf.len + 1);
 
 		pfree(buf.data);
 	}
 
-	pq_endmessage(&msgbuf);
-
 	/*
 	 * This flush is normally not necessary, since postgres.c will flush out
 	 * waiting data when control returns to the main loop. But it seems best
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 4bcf705a30..3fd1a5fbe2 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -6306,11 +6306,9 @@ BeginReportingGUCOptions(void)
 	int			i;
 
 	/*
-	 * Don't do anything unless talking to an interactive frontend of protocol
-	 * 3.0 or later.
+	 * Don't do anything unless talking to an interactive frontend.
 	 */
-	if (whereToSendOutput != DestRemote ||
-		PG_PROTOCOL_MAJOR(FrontendProtocol) < 3)
+	if (whereToSendOutput != DestRemote)
 		return;
 
 	reporting_enabled = true;
diff --git a/src/bin/psql/common.c b/src/bin/psql/common.c
index 925fe34a3f..7a95465111 100644
--- a/src/bin/psql/common.c
+++ b/src/bin/psql/common.c
@@ -2154,9 +2154,6 @@ is_select_command(const char *query)
 
 /*
  * Test if the current user is a database superuser.
- *
- * Note: this will correctly detect superuserness only with a protocol-3.0
- * or newer backend; otherwise it will always say "false".
  */
 bool
 is_superuser(void)
@@ -2177,9 +2174,6 @@ is_superuser(void)
 
 /*
  * Test if the current session uses standard string literals.
- *
- * Note: With a pre-protocol-3.0 connection this will always say "false",
- * which should be the right answer.
  */
 bool
 standard_strings(void)
@@ -2200,10 +2194,6 @@ standard_strings(void)
 
 /*
  * Return the session user of the current connection.
- *
- * Note: this will correctly detect the session user only with a
- * protocol-3.0 or newer backend; otherwise it will return the
- * connection user.
  */
 const char *
 session_username(void)
diff --git a/src/bin/psql/copy.c b/src/bin/psql/copy.c
index 78f0dc5a50..e1fee8e099 100644
--- a/src/bin/psql/copy.c
+++ b/src/bin/psql/copy.c
@@ -662,7 +662,9 @@ handleCopyIn(PGconn *conn, FILE *copystream, bool isbinary, PGresult **res)
 
 	/*
 	 * Terminate data transfer.  We can't send an error message if we're using
-	 * protocol version 2.
+	 * protocol version 2.  (libpq no longer supports protocol version 2, but
+	 * keep the version checks just in case you're using a pre-v14 libpq.so at
+	 * runtime)
 	 */
 	if (PQputCopyEnd(conn,
 					 (OK || PQprotocolVersion(conn) < 3) ? NULL :
diff --git a/src/include/commands/copyfrom_internal.h b/src/include/commands/copyfrom_internal.h
index e37942df39..705f5b615b 100644
--- a/src/include/commands/copyfrom_internal.h
+++ b/src/include/commands/copyfrom_internal.h
@@ -24,8 +24,7 @@
 typedef enum CopySource
 {
 	COPY_FILE,					/* from file (or a piped program) */
-	COPY_OLD_FE,				/* from frontend (2.0 protocol) */
-	COPY_NEW_FE,				/* from frontend (3.0 protocol) */
+	COPY_FRONTEND,				/* from frontend */
 	COPY_CALLBACK				/* from callback function */
 } CopySource;
 
diff --git a/src/include/libpq/libpq.h b/src/include/libpq/libpq.h
index e4e5c21565..b20deeb555 100644
--- a/src/include/libpq/libpq.h
+++ b/src/include/libpq/libpq.h
@@ -29,8 +29,6 @@ typedef struct
 	bool		(*is_send_pending) (void);
 	int			(*putmessage) (char msgtype, const char *s, size_t len);
 	void		(*putmessage_noblock) (char msgtype, const char *s, size_t len);
-	void		(*startcopyout) (void);
-	void		(*endcopyout) (bool errorAbort);
 } PQcommMethods;
 
 extern const PGDLLIMPORT PQcommMethods *PqCommMethods;
@@ -43,8 +41,6 @@ extern const PGDLLIMPORT PQcommMethods *PqCommMethods;
 	(PqCommMethods->putmessage(msgtype, s, len))
 #define pq_putmessage_noblock(msgtype, s, len) \
 	(PqCommMethods->putmessage_noblock(msgtype, s, len))
-#define pq_startcopyout() (PqCommMethods->startcopyout())
-#define pq_endcopyout(errorAbort) (PqCommMethods->endcopyout(errorAbort))
 
 /*
  * External functions.
@@ -67,7 +63,6 @@ extern void TouchSocketFiles(void);
 extern void RemoveSocketFiles(void);
 extern void pq_init(void);
 extern int	pq_getbytes(char *s, size_t len);
-extern int	pq_getstring(StringInfo s);
 extern void pq_startmsgread(void);
 extern void pq_endmsgread(void);
 extern bool pq_is_reading_msg(void);
@@ -75,7 +70,7 @@ extern int	pq_getmessage(StringInfo s, int maxlen);
 extern int	pq_getbyte(void);
 extern int	pq_peekbyte(void);
 extern int	pq_getbyte_if_available(unsigned char *c);
-extern int	pq_putbytes(const char *s, size_t len);
+extern int	pq_putmessage_v2(char msgtype, const char *s, size_t len);
 
 /*
  * prototypes for functions in be-secure.c
diff --git a/src/include/libpq/pqcomm.h b/src/include/libpq/pqcomm.h
index a86b895b26..be9d970574 100644
--- a/src/include/libpq/pqcomm.h
+++ b/src/include/libpq/pqcomm.h
@@ -114,9 +114,12 @@ is_unixsock_path(const char *path)
 #define PG_PROTOCOL_MINOR(v)	((v) & 0x0000ffff)
 #define PG_PROTOCOL(m,n)	(((m) << 16) | (n))
 
-/* The earliest and latest frontend/backend protocol version supported. */
+/*
+ * The earliest and latest frontend/backend protocol version supported.
+ * (Only protocol version 3 is currently supported)
+ */
 
-#define PG_PROTOCOL_EARLIEST	PG_PROTOCOL(2,0)
+#define PG_PROTOCOL_EARLIEST	PG_PROTOCOL(3,0)
 #define PG_PROTOCOL_LATEST		PG_PROTOCOL(3,0)
 
 typedef uint32 ProtocolVersion; /* FE/BE protocol version number */
@@ -132,32 +135,6 @@ typedef ProtocolVersion MsgType;
 
 typedef uint32 PacketLen;
 
-
-/*
- * Old-style startup packet layout with fixed-width fields.  This is used in
- * protocol 1.0 and 2.0, but not in later versions.  Note that the fields
- * in this layout are '\0' terminated only if there is room.
- */
-
-#define SM_DATABASE		64
-#define SM_USER			32
-/* We append database name if db_user_namespace true. */
-#define SM_DATABASE_USER (SM_DATABASE+SM_USER+1)	/* +1 for @ */
-#define SM_OPTIONS		64
-#define SM_UNUSED		64
-#define SM_TTY			64
-
-typedef struct StartupPacket
-{
-	ProtocolVersion protoVersion;	/* Protocol version */
-	char		database[SM_DATABASE];	/* Database name */
-	/* Db_user_namespace appends dbname */
-	char		user[SM_USER];	/* User name */
-	char		options[SM_OPTIONS];	/* Optional additional args */
-	char		unused[SM_UNUSED];	/* Unused */
-	char		tty[SM_TTY];	/* Tty for debug output */
-} StartupPacket;
-
 extern bool Db_user_namespace;
 
 /*
diff --git a/src/include/tcop/fastpath.h b/src/include/tcop/fastpath.h
index b67c44fe69..c4d7a47dd7 100644
--- a/src/include/tcop/fastpath.h
+++ b/src/include/tcop/fastpath.h
@@ -15,7 +15,6 @@
 
 #include "lib/stringinfo.h"
 
-extern int	GetOldFunctionMessage(StringInfo buf);
 extern void HandleFunctionRequest(StringInfo msgBuf);
 
 #endif							/* FASTPATH_H */
diff --git a/src/include/utils/elog.h b/src/include/utils/elog.h
index 3c0e57621f..b59651289e 100644
--- a/src/include/utils/elog.h
+++ b/src/include/utils/elog.h
@@ -207,7 +207,6 @@ extern int	errhidecontext(bool hide_ctx);
 
 extern int	errbacktrace(void);
 
-extern int	errfunction(const char *funcname);
 extern int	errposition(int cursorpos);
 
 extern int	internalerrposition(int cursorpos);
@@ -367,7 +366,6 @@ typedef struct ErrorData
 	int			elevel;			/* error level */
 	bool		output_to_server;	/* will report to server log? */
 	bool		output_to_client;	/* will report to client? */
-	bool		show_funcname;	/* true to force funcname inclusion */
 	bool		hide_stmt;		/* true to prevent STATEMENT: inclusion */
 	bool		hide_ctx;		/* true to prevent CONTEXT: inclusion */
 	const char *filename;		/* __FILE__ of ereport() call */
diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile
index f74677eaf9..2aca882a2b 100644
--- a/src/interfaces/libpq/Makefile
+++ b/src/interfaces/libpq/Makefile
@@ -37,7 +37,6 @@ OBJS = \
 	fe-lobj.o \
 	fe-misc.o \
 	fe-print.o \
-	fe-protocol2.o \
 	fe-protocol3.o \
 	fe-secure.o \
 	legacy-pqsignal.o \
diff --git a/src/interfaces/libpq/fe-auth.c b/src/interfaces/libpq/fe-auth.c
index 168b3df52b..e8062647e6 100644
--- a/src/interfaces/libpq/fe-auth.c
+++ b/src/interfaces/libpq/fe-auth.c
@@ -579,7 +579,7 @@ pg_SASL_init(PGconn *conn, int payloadlen)
 	/*
 	 * Build a SASLInitialResponse message, and send it.
 	 */
-	if (pqPutMsgStart('p', true, conn))
+	if (pqPutMsgStart('p', conn))
 		goto error;
 	if (pqPuts(selected_mechanism, conn))
 		goto error;
@@ -798,11 +798,7 @@ pg_password_sendauth(PGconn *conn, const char *password, AuthRequest areq)
 		default:
 			return STATUS_ERROR;
 	}
-	/* Packet has a message type as of protocol 3.0 */
-	if (PG_PROTOCOL_MAJOR(conn->pversion) >= 3)
-		ret = pqPacketSend(conn, 'p', pwd_to_send, strlen(pwd_to_send) + 1);
-	else
-		ret = pqPacketSend(conn, 0, pwd_to_send, strlen(pwd_to_send) + 1);
+	ret = pqPacketSend(conn, 'p', pwd_to_send, strlen(pwd_to_send) + 1);
 	if (crypt_pwd)
 		free(crypt_pwd);
 	return ret;
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index 9812a14662..8ef5e11cb3 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -2289,10 +2289,6 @@ PQconnectPoll(PGconn *conn)
 		case CONNECTION_MADE:
 			break;
 
-			/* We allow pqSetenvPoll to decide whether to proceed. */
-		case CONNECTION_SETENV:
-			break;
-
 			/* Special cases: proceed without waiting. */
 		case CONNECTION_SSL_STARTUP:
 		case CONNECTION_NEEDED:
@@ -2956,12 +2952,8 @@ keep_going:						/* We will come back to here until there is
 				/*
 				 * Build the startup packet.
 				 */
-				if (PG_PROTOCOL_MAJOR(conn->pversion) >= 3)
-					startpacket = pqBuildStartupPacket3(conn, &packetlen,
-														EnvironmentOptions);
-				else
-					startpacket = pqBuildStartupPacket2(conn, &packetlen,
-														EnvironmentOptions);
+				startpacket = pqBuildStartupPacket3(conn, &packetlen,
+													EnvironmentOptions);
 				if (!startpacket)
 				{
 					appendPQExpBufferStr(&conn->errorMessage,
@@ -3247,19 +3239,11 @@ keep_going:						/* We will come back to here until there is
 					goto error_return;
 				}
 
-				if (PG_PROTOCOL_MAJOR(conn->pversion) >= 3)
-				{
-					/* Read message length word */
-					if (pqGetInt(&msgLength, 4, conn))
-					{
-						/* We'll come back when there is more data */
-						return PGRES_POLLING_READING;
-					}
-				}
-				else
+				/* Read message length word */
+				if (pqGetInt(&msgLength, 4, conn))
 				{
-					/* Set phony message length to disable checks below */
-					msgLength = 8;
+					/* We'll come back when there is more data */
+					return PGRES_POLLING_READING;
 				}
 
 				/*
@@ -3268,7 +3252,9 @@ keep_going:						/* We will come back to here until there is
 				 * auth requests may not be that small.  Errors can be a
 				 * little larger, but not huge.  If we see a large apparent
 				 * length in an error, it means we're really talking to a
-				 * pre-3.0-protocol server; cope.
+				 * pre-3.0-protocol server; cope.  (Before version 14, the
+				 * server also used the old protocol for errors that happened
+				 * before processing the startup packet.)
 				 */
 				if (beresp == 'R' && (msgLength < 8 || msgLength > 2000))
 				{
@@ -3296,25 +3282,11 @@ keep_going:						/* We will come back to here until there is
 					 */
 					appendPQExpBufferChar(&conn->errorMessage, '\n');
 
-					/*
-					 * If we tried to open the connection in 3.0 protocol,
-					 * fall back to 2.0 protocol.
-					 */
-					if (PG_PROTOCOL_MAJOR(conn->pversion) >= 3)
-					{
-						conn->pversion = PG_PROTOCOL(2, 0);
-						need_new_connection = true;
-						goto keep_going;
-					}
-
 					goto error_return;
 				}
 
 				/*
 				 * Can't process if message body isn't all here yet.
-				 *
-				 * (In protocol 2.0 case, we are assuming messages carry at
-				 * least 4 bytes of data.)
 				 */
 				msgLength -= 4;
 				avail = conn->inEnd - conn->inCursor;
@@ -3335,21 +3307,10 @@ keep_going:						/* We will come back to here until there is
 				/* Handle errors. */
 				if (beresp == 'E')
 				{
-					if (PG_PROTOCOL_MAJOR(conn->pversion) >= 3)
+					if (pqGetErrorNotice3(conn, true))
 					{
-						if (pqGetErrorNotice3(conn, true))
-						{
-							/* We'll come back when there is more data */
-							return PGRES_POLLING_READING;
-						}
-					}
-					else
-					{
-						if (pqGets_append(&conn->errorMessage, conn))
-						{
-							/* We'll come back when there is more data */
-							return PGRES_POLLING_READING;
-						}
+						/* We'll come back when there is more data */
+						return PGRES_POLLING_READING;
 					}
 					/* OK, we read the message; mark data consumed */
 					conn->inStart = conn->inCursor;
@@ -3433,33 +3394,6 @@ keep_going:						/* We will come back to here until there is
 				}
 				msgLength -= 4;
 
-				/*
-				 * Ensure the password salt is in the input buffer, if it's an
-				 * MD5 request.  All the other authentication methods that
-				 * contain extra data in the authentication request are only
-				 * supported in protocol version 3, in which case we already
-				 * read the whole message above.
-				 */
-				if (areq == AUTH_REQ_MD5 && PG_PROTOCOL_MAJOR(conn->pversion) < 3)
-				{
-					msgLength += 4;
-
-					avail = conn->inEnd - conn->inCursor;
-					if (avail < 4)
-					{
-						/*
-						 * Before returning, try to enlarge the input buffer
-						 * if needed to hold the whole message; see notes in
-						 * pqParseInput3.
-						 */
-						if (pqCheckInBufferSpace(conn->inCursor + (size_t) 4,
-												 conn))
-							goto error_return;
-						/* We'll come back when there is more data */
-						return PGRES_POLLING_READING;
-					}
-				}
-
 				/*
 				 * Process the rest of the authentication request message, and
 				 * respond to it if necessary.
@@ -3567,15 +3501,6 @@ keep_going:						/* We will come back to here until there is
 					goto error_return;
 				}
 
-				/* Fire up post-connection housekeeping if needed */
-				if (PG_PROTOCOL_MAJOR(conn->pversion) < 3)
-				{
-					conn->status = CONNECTION_SETENV;
-					conn->setenv_state = SETENV_STATE_CLIENT_ENCODING_SEND;
-					conn->next_eo = EnvironmentOptions;
-					return PGRES_POLLING_WRITING;
-				}
-
 				/* Almost there now ... */
 				conn->status = CONNECTION_CHECK_TARGET;
 				goto keep_going;
@@ -3596,17 +3521,9 @@ keep_going:						/* We will come back to here until there is
 					 * If the server didn't report
 					 * "default_transaction_read_only" or "in_hot_standby" at
 					 * startup, we must determine its state by sending the
-					 * query "SHOW transaction_read_only".  Servers before 7.4
-					 * lack the transaction_read_only GUC, but by the same
-					 * token they don't have any read-only mode, so we may
-					 * just assume the results.
+					 * query "SHOW transaction_read_only".  This GUC exists
+					 * in all server versions that support 3.0 protocol.
 					 */
-					if (conn->sversion < 70400)
-					{
-						conn->default_transaction_read_only = PG_BOOL_NO;
-						conn->in_hot_standby = PG_BOOL_NO;
-					}
-
 					if (conn->default_transaction_read_only == PG_BOOL_UNKNOWN ||
 						conn->in_hot_standby == PG_BOOL_UNKNOWN)
 					{
@@ -3719,39 +3636,6 @@ keep_going:						/* We will come back to here until there is
 				return PGRES_POLLING_OK;
 			}
 
-		case CONNECTION_SETENV:
-			{
-				/*
-				 * Do post-connection housekeeping (only needed in protocol
-				 * 2.0).
-				 *
-				 * We pretend that the connection is OK for the duration of
-				 * these queries.
-				 */
-				conn->status = CONNECTION_OK;
-
-				switch (pqSetenvPoll(conn))
-				{
-					case PGRES_POLLING_OK:	/* Success */
-						break;
-
-					case PGRES_POLLING_READING: /* Still going */
-						conn->status = CONNECTION_SETENV;
-						return PGRES_POLLING_READING;
-
-					case PGRES_POLLING_WRITING: /* Still going */
-						conn->status = CONNECTION_SETENV;
-						return PGRES_POLLING_WRITING;
-
-					default:
-						goto error_return;
-				}
-
-				/* Almost there now ... */
-				conn->status = CONNECTION_CHECK_TARGET;
-				goto keep_going;
-			}
-
 		case CONNECTION_CONSUME:
 			{
 				/*
@@ -4042,7 +3926,6 @@ makeEmptyPGconn(void)
 	conn->xactStatus = PQTRANS_IDLE;
 	conn->options_valid = false;
 	conn->nonblocking = false;
-	conn->setenv_state = SETENV_STATE_IDLE;
 	conn->client_encoding = PG_SQL_ASCII;
 	conn->std_strings = false;	/* unless server says differently */
 	conn->default_transaction_read_only = PG_BOOL_UNKNOWN;
@@ -4259,7 +4142,7 @@ sendTerminateConn(PGconn *conn)
 		 * Try to send "close connection" message to backend. Ignore any
 		 * error.
 		 */
-		pqPutMsgStart('X', false, conn);
+		pqPutMsgStart('X', conn);
 		pqPutMsgEnd(conn);
 		(void) pqFlush(conn);
 	}
@@ -4652,16 +4535,13 @@ PQrequestCancel(PGconn *conn)
  *
  * RETURNS: STATUS_ERROR if the write fails, STATUS_OK otherwise.
  * SIDE_EFFECTS: may block.
- *
- * Note: all messages sent with this routine have a length word, whether
- * it's protocol 2.0 or 3.0.
  */
 int
 pqPacketSend(PGconn *conn, char pack_type,
 			 const void *buf, size_t buf_len)
 {
 	/* Start the message. */
-	if (pqPutMsgStart(pack_type, true, conn))
+	if (pqPutMsgStart(pack_type, conn))
 		return STATUS_ERROR;
 
 	/* Send the message body. */
@@ -6917,13 +6797,9 @@ PQsetClientEncoding(PGconn *conn, const char *encoding)
 	else
 	{
 		/*
-		 * In protocol 2 we have to assume the setting will stick, and adjust
-		 * our state immediately.  In protocol 3 and up we can rely on the
-		 * backend to report the parameter value, and we'll change state at
-		 * that time.
+		 * We rely on the backend to report the parameter value, and we'll
+		 * change state at that time.
 		 */
-		if (PG_PROTOCOL_MAJOR(conn->pversion) < 3)
-			pqSaveParameterStatus(conn, "client_encoding", encoding);
 		status = 0;				/* everything is ok */
 	}
 	PQclear(res);
diff --git a/src/interfaces/libpq/fe-exec.c b/src/interfaces/libpq/fe-exec.c
index a550753855..9a038043b2 100644
--- a/src/interfaces/libpq/fe-exec.c
+++ b/src/interfaces/libpq/fe-exec.c
@@ -1221,7 +1221,7 @@ PQsendQueryInternal(PGconn *conn, const char *query, bool newQuery)
 	}
 
 	/* construct the outgoing Query message */
-	if (pqPutMsgStart('Q', false, conn) < 0 ||
+	if (pqPutMsgStart('Q', conn) < 0 ||
 		pqPuts(query, conn) < 0 ||
 		pqPutMsgEnd(conn) < 0)
 	{
@@ -1255,7 +1255,7 @@ PQsendQueryInternal(PGconn *conn, const char *query, bool newQuery)
 
 /*
  * PQsendQueryParams
- *		Like PQsendQuery, but use protocol 3.0 so we can pass parameters
+ *		Like PQsendQuery, but use extended query protocol so we can pass parameters
  */
 int
 PQsendQueryParams(PGconn *conn,
@@ -1330,16 +1330,8 @@ PQsendPrepare(PGconn *conn,
 		return 0;
 	}
 
-	/* This isn't gonna work on a 2.0 server */
-	if (PG_PROTOCOL_MAJOR(conn->pversion) < 3)
-	{
-		appendPQExpBufferStr(&conn->errorMessage,
-							 libpq_gettext("function requires at least protocol version 3.0\n"));
-		return 0;
-	}
-
 	/* construct the Parse message */
-	if (pqPutMsgStart('P', false, conn) < 0 ||
+	if (pqPutMsgStart('P', conn) < 0 ||
 		pqPuts(stmtName, conn) < 0 ||
 		pqPuts(query, conn) < 0)
 		goto sendFailed;
@@ -1365,7 +1357,7 @@ PQsendPrepare(PGconn *conn,
 		goto sendFailed;
 
 	/* construct the Sync message */
-	if (pqPutMsgStart('S', false, conn) < 0 ||
+	if (pqPutMsgStart('S', conn) < 0 ||
 		pqPutMsgEnd(conn) < 0)
 		goto sendFailed;
 
@@ -1397,7 +1389,7 @@ sendFailed:
 /*
  * PQsendQueryPrepared
  *		Like PQsendQuery, but execute a previously prepared statement,
- *		using protocol 3.0 so we can pass parameters
+ *		using extended query protocol so we can pass parameters
  */
 int
 PQsendQueryPrepared(PGconn *conn,
@@ -1478,7 +1470,7 @@ PQsendQueryStart(PGconn *conn, bool newQuery)
 
 /*
  * PQsendQueryGuts
- *		Common code for protocol-3.0 query sending
+ *		Common code for sending a query with extended query protocol
  *		PQsendQueryStart should be done already
  *
  * command may be NULL to indicate we use an already-prepared statement
@@ -1496,14 +1488,6 @@ PQsendQueryGuts(PGconn *conn,
 {
 	int			i;
 
-	/* This isn't gonna work on a 2.0 server */
-	if (PG_PROTOCOL_MAJOR(conn->pversion) < 3)
-	{
-		appendPQExpBufferStr(&conn->errorMessage,
-							 libpq_gettext("function requires at least protocol version 3.0\n"));
-		return 0;
-	}
-
 	/*
 	 * We will send Parse (if needed), Bind, Describe Portal, Execute, Sync,
 	 * using specified statement name and the unnamed portal.
@@ -1512,7 +1496,7 @@ PQsendQueryGuts(PGconn *conn,
 	if (command)
 	{
 		/* construct the Parse message */
-		if (pqPutMsgStart('P', false, conn) < 0 ||
+		if (pqPutMsgStart('P', conn) < 0 ||
 			pqPuts(stmtName, conn) < 0 ||
 			pqPuts(command, conn) < 0)
 			goto sendFailed;
@@ -1536,7 +1520,7 @@ PQsendQueryGuts(PGconn *conn,
 	}
 
 	/* Construct the Bind message */
-	if (pqPutMsgStart('B', false, conn) < 0 ||
+	if (pqPutMsgStart('B', conn) < 0 ||
 		pqPuts("", conn) < 0 ||
 		pqPuts(stmtName, conn) < 0)
 		goto sendFailed;
@@ -1603,21 +1587,21 @@ PQsendQueryGuts(PGconn *conn,
 		goto sendFailed;
 
 	/* construct the Describe Portal message */
-	if (pqPutMsgStart('D', false, conn) < 0 ||
+	if (pqPutMsgStart('D', conn) < 0 ||
 		pqPutc('P', conn) < 0 ||
 		pqPuts("", conn) < 0 ||
 		pqPutMsgEnd(conn) < 0)
 		goto sendFailed;
 
 	/* construct the Execute message */
-	if (pqPutMsgStart('E', false, conn) < 0 ||
+	if (pqPutMsgStart('E', conn) < 0 ||
 		pqPuts("", conn) < 0 ||
 		pqPutInt(0, 4, conn) < 0 ||
 		pqPutMsgEnd(conn) < 0)
 		goto sendFailed;
 
 	/* construct the Sync message */
-	if (pqPutMsgStart('S', false, conn) < 0 ||
+	if (pqPutMsgStart('S', conn) < 0 ||
 		pqPutMsgEnd(conn) < 0)
 		goto sendFailed;
 
@@ -1718,10 +1702,7 @@ PQconsumeInput(PGconn *conn)
 static void
 parseInput(PGconn *conn)
 {
-	if (PG_PROTOCOL_MAJOR(conn->pversion) >= 3)
-		pqParseInput3(conn);
-	else
-		pqParseInput2(conn);
+	pqParseInput3(conn);
 }
 
 /*
@@ -1926,7 +1907,7 @@ PQexec(PGconn *conn, const char *query)
 
 /*
  * PQexecParams
- *		Like PQexec, but use protocol 3.0 so we can pass parameters
+ *		Like PQexec, but use extended query protocol so we can pass parameters
  */
 PGresult *
 PQexecParams(PGconn *conn,
@@ -1949,7 +1930,7 @@ PQexecParams(PGconn *conn,
 
 /*
  * PQprepare
- *	  Creates a prepared statement by issuing a v3.0 parse message.
+ *	  Creates a prepared statement by issuing a Parse message.
  *
  * If the query was not even sent, return NULL; conn->errorMessage is set to
  * a relevant message.
@@ -1973,7 +1954,7 @@ PQprepare(PGconn *conn,
 /*
  * PQexecPrepared
  *		Like PQexec, but execute a previously prepared statement,
- *		using protocol 3.0 so we can pass parameters
+ *		using extended query protocol so we can pass parameters
  */
 PGresult *
 PQexecPrepared(PGconn *conn,
@@ -2020,41 +2001,20 @@ PQexecStart(PGconn *conn)
 		PQclear(result);		/* only need its status */
 		if (resultStatus == PGRES_COPY_IN)
 		{
-			if (PG_PROTOCOL_MAJOR(conn->pversion) >= 3)
-			{
-				/* In protocol 3, we can get out of a COPY IN state */
-				if (PQputCopyEnd(conn,
-								 libpq_gettext("COPY terminated by new PQexec")) < 0)
-					return false;
-				/* keep waiting to swallow the copy's failure message */
-			}
-			else
-			{
-				/* In older protocols we have to punt */
-				appendPQExpBufferStr(&conn->errorMessage,
-									 libpq_gettext("COPY IN state must be terminated first\n"));
+			/* get out of a COPY IN state */
+			if (PQputCopyEnd(conn,
+							 libpq_gettext("COPY terminated by new PQexec")) < 0)
 				return false;
-			}
+			/* keep waiting to swallow the copy's failure message */
 		}
 		else if (resultStatus == PGRES_COPY_OUT)
 		{
-			if (PG_PROTOCOL_MAJOR(conn->pversion) >= 3)
-			{
-				/*
-				 * In protocol 3, we can get out of a COPY OUT state: we just
-				 * switch back to BUSY and allow the remaining COPY data to be
-				 * dropped on the floor.
-				 */
-				conn->asyncStatus = PGASYNC_BUSY;
-				/* keep waiting to swallow the copy's completion message */
-			}
-			else
-			{
-				/* In older protocols we have to punt */
-				appendPQExpBufferStr(&conn->errorMessage,
-									 libpq_gettext("COPY OUT state must be terminated first\n"));
-				return false;
-			}
+			/*
+			 * Get out of a COPY OUT state: we just switch back to BUSY and
+			 * allow the remaining COPY data to be dropped on the floor.
+			 */
+			conn->asyncStatus = PGASYNC_BUSY;
+			/* keep waiting to swallow the copy's completion message */
 		}
 		else if (resultStatus == PGRES_COPY_BOTH)
 		{
@@ -2195,23 +2155,15 @@ PQsendDescribe(PGconn *conn, char desc_type, const char *desc_target)
 	if (!PQsendQueryStart(conn, true))
 		return 0;
 
-	/* This isn't gonna work on a 2.0 server */
-	if (PG_PROTOCOL_MAJOR(conn->pversion) < 3)
-	{
-		appendPQExpBufferStr(&conn->errorMessage,
-							 libpq_gettext("function requires at least protocol version 3.0\n"));
-		return 0;
-	}
-
 	/* construct the Describe message */
-	if (pqPutMsgStart('D', false, conn) < 0 ||
+	if (pqPutMsgStart('D', conn) < 0 ||
 		pqPutc(desc_type, conn) < 0 ||
 		pqPuts(desc_target, conn) < 0 ||
 		pqPutMsgEnd(conn) < 0)
 		goto sendFailed;
 
 	/* construct the Sync message */
-	if (pqPutMsgStart('S', false, conn) < 0 ||
+	if (pqPutMsgStart('S', conn) < 0 ||
 		pqPutMsgEnd(conn) < 0)
 		goto sendFailed;
 
@@ -2311,8 +2263,7 @@ PQputCopyData(PGconn *conn, const char *buffer, int nbytes)
 		 * Try to flush any previously sent data in preference to growing the
 		 * output buffer.  If we can't enlarge the buffer enough to hold the
 		 * data, return 0 in the nonblock case, else hard error. (For
-		 * simplicity, always assume 5 bytes of overhead even in protocol 2.0
-		 * case.)
+		 * simplicity, always assume 5 bytes of overhead.)
 		 */
 		if ((conn->outBufSize - conn->outCount - 5) < nbytes)
 		{
@@ -2323,20 +2274,10 @@ PQputCopyData(PGconn *conn, const char *buffer, int nbytes)
 				return pqIsnonblocking(conn) ? 0 : -1;
 		}
 		/* Send the data (too simple to delegate to fe-protocol files) */
-		if (PG_PROTOCOL_MAJOR(conn->pversion) >= 3)
-		{
-			if (pqPutMsgStart('d', false, conn) < 0 ||
-				pqPutnchar(buffer, nbytes, conn) < 0 ||
-				pqPutMsgEnd(conn) < 0)
-				return -1;
-		}
-		else
-		{
-			if (pqPutMsgStart(0, false, conn) < 0 ||
-				pqPutnchar(buffer, nbytes, conn) < 0 ||
-				pqPutMsgEnd(conn) < 0)
-				return -1;
-		}
+		if (pqPutMsgStart('d', conn) < 0 ||
+			pqPutnchar(buffer, nbytes, conn) < 0 ||
+			pqPutMsgEnd(conn) < 0)
+			return -1;
 	}
 	return 1;
 }
@@ -2366,52 +2307,31 @@ PQputCopyEnd(PGconn *conn, const char *errormsg)
 	 * Send the COPY END indicator.  This is simple enough that we don't
 	 * bother delegating it to the fe-protocol files.
 	 */
-	if (PG_PROTOCOL_MAJOR(conn->pversion) >= 3)
+	if (errormsg)
 	{
-		if (errormsg)
-		{
-			/* Send COPY FAIL */
-			if (pqPutMsgStart('f', false, conn) < 0 ||
-				pqPuts(errormsg, conn) < 0 ||
-				pqPutMsgEnd(conn) < 0)
-				return -1;
-		}
-		else
-		{
-			/* Send COPY DONE */
-			if (pqPutMsgStart('c', false, conn) < 0 ||
-				pqPutMsgEnd(conn) < 0)
-				return -1;
-		}
-
-		/*
-		 * If we sent the COPY command in extended-query mode, we must issue a
-		 * Sync as well.
-		 */
-		if (conn->queryclass != PGQUERY_SIMPLE)
-		{
-			if (pqPutMsgStart('S', false, conn) < 0 ||
-				pqPutMsgEnd(conn) < 0)
-				return -1;
-		}
+		/* Send COPY FAIL */
+		if (pqPutMsgStart('f', conn) < 0 ||
+			pqPuts(errormsg, conn) < 0 ||
+			pqPutMsgEnd(conn) < 0)
+			return -1;
 	}
 	else
 	{
-		if (errormsg)
-		{
-			/* Oops, no way to do this in 2.0 */
-			appendPQExpBufferStr(&conn->errorMessage,
-								 libpq_gettext("function requires at least protocol version 3.0\n"));
+		/* Send COPY DONE */
+		if (pqPutMsgStart('c', conn) < 0 ||
+			pqPutMsgEnd(conn) < 0)
+			return -1;
+	}
+
+	/*
+	 * If we sent the COPY command in extended-query mode, we must issue a
+	 * Sync as well.
+	 */
+	if (conn->queryclass != PGQUERY_SIMPLE)
+	{
+		if (pqPutMsgStart('S', conn) < 0 ||
+			pqPutMsgEnd(conn) < 0)
 			return -1;
-		}
-		else
-		{
-			/* Send old-style end-of-data marker */
-			if (pqPutMsgStart(0, false, conn) < 0 ||
-				pqPutnchar("\\.\n", 3, conn) < 0 ||
-				pqPutMsgEnd(conn) < 0)
-				return -1;
-		}
 	}
 
 	/* Return to active duty */
@@ -2450,10 +2370,7 @@ PQgetCopyData(PGconn *conn, char **buffer, int async)
 							 libpq_gettext("no COPY in progress\n"));
 		return -2;
 	}
-	if (PG_PROTOCOL_MAJOR(conn->pversion) >= 3)
-		return pqGetCopyData3(conn, buffer, async);
-	else
-		return pqGetCopyData2(conn, buffer, async);
+	return pqGetCopyData3(conn, buffer, async);
 }
 
 /*
@@ -2492,10 +2409,7 @@ PQgetline(PGconn *conn, char *s, int maxlen)
 	if (!conn)
 		return EOF;
 
-	if (PG_PROTOCOL_MAJOR(conn->pversion) >= 3)
-		return pqGetline3(conn, s, maxlen);
-	else
-		return pqGetline2(conn, s, maxlen);
+	return pqGetline3(conn, s, maxlen);
 }
 
 /*
@@ -2535,10 +2449,7 @@ PQgetlineAsync(PGconn *conn, char *buffer, int bufsize)
 	if (!conn)
 		return -1;
 
-	if (PG_PROTOCOL_MAJOR(conn->pversion) >= 3)
-		return pqGetlineAsync3(conn, buffer, bufsize);
-	else
-		return pqGetlineAsync2(conn, buffer, bufsize);
+	return pqGetlineAsync3(conn, buffer, bufsize);
 }
 
 /*
@@ -2573,10 +2484,8 @@ PQputnbytes(PGconn *conn, const char *buffer, int nbytes)
  *		After completing the data transfer portion of a copy in/out,
  *		the application must call this routine to finish the command protocol.
  *
- * When using protocol 3.0 this is deprecated; it's cleaner to use PQgetResult
- * to get the transfer status.  Note however that when using 2.0 protocol,
- * recovering from a copy failure often requires a PQreset.  PQendcopy will
- * take care of that, PQgetResult won't.
+ * This is deprecated; it's cleaner to use PQgetResult to get the transfer
+ * status.
  *
  * RETURNS:
  *		0 on success
@@ -2588,10 +2497,7 @@ PQendcopy(PGconn *conn)
 	if (!conn)
 		return 0;
 
-	if (PG_PROTOCOL_MAJOR(conn->pversion) >= 3)
-		return pqEndcopy3(conn);
-	else
-		return pqEndcopy2(conn);
+	return pqEndcopy3(conn);
 }
 
 
@@ -2643,16 +2549,10 @@ PQfn(PGconn *conn,
 		return NULL;
 	}
 
-	if (PG_PROTOCOL_MAJOR(conn->pversion) >= 3)
-		return pqFunctionCall3(conn, fnid,
-							   result_buf, result_len,
-							   result_is_int,
-							   args, nargs);
-	else
-		return pqFunctionCall2(conn, fnid,
-							   result_buf, result_len,
-							   result_is_int,
-							   args, nargs);
+	return pqFunctionCall3(conn, fnid,
+						   result_buf, result_len,
+						   result_is_int,
+						   args, nargs);
 }
 
 
@@ -2701,13 +2601,6 @@ PQresultVerboseErrorMessage(const PGresult *res,
 
 	initPQExpBuffer(&workBuf);
 
-	/*
-	 * Currently, we pass this off to fe-protocol3.c in all cases; it will
-	 * behave reasonably sanely with an error reported by fe-protocol2.c as
-	 * well.  If necessary, we could record the protocol version in PGresults
-	 * so as to be able to invoke a version-specific message formatter, but
-	 * for now there's no need.
-	 */
 	pqBuildErrorMessage3(&workBuf, res, verbosity, show_context);
 
 	/* If insufficient memory to format the message, fail cleanly */
diff --git a/src/interfaces/libpq/fe-lobj.c b/src/interfaces/libpq/fe-lobj.c
index b9c52eb50c..13c2f10af1 100644
--- a/src/interfaces/libpq/fe-lobj.c
+++ b/src/interfaces/libpq/fe-lobj.c
@@ -884,11 +884,9 @@ lo_initialize(PGconn *conn)
 	MemSet((char *) lobjfuncs, 0, sizeof(PGlobjfuncs));
 
 	/*
-	 * Execute the query to get all the functions at once.  In 7.3 and later
-	 * we need to be schema-safe.  lo_create only exists in 8.1 and up.
-	 * lo_truncate only exists in 8.3 and up.
+	 * Execute the query to get all the functions at once.  (Not all of them
+	 * may exist in older server versions.)
 	 */
-	if (conn->sversion >= 70300)
 		query = "select proname, oid from pg_catalog.pg_proc "
 			"where proname in ("
 			"'lo_open', "
@@ -906,16 +904,6 @@ lo_initialize(PGconn *conn)
 			"'lowrite') "
 			"and pronamespace = (select oid from pg_catalog.pg_namespace "
 			"where nspname = 'pg_catalog')";
-	else
-		query = "select proname, oid from pg_proc "
-			"where proname = 'lo_open' "
-			"or proname = 'lo_close' "
-			"or proname = 'lo_creat' "
-			"or proname = 'lo_unlink' "
-			"or proname = 'lo_lseek' "
-			"or proname = 'lo_tell' "
-			"or proname = 'loread' "
-			"or proname = 'lowrite'";
 
 	res = PQexec(conn, query);
 	if (res == NULL)
diff --git a/src/interfaces/libpq/fe-misc.c b/src/interfaces/libpq/fe-misc.c
index 2bfb6acd89..ce2d24b91f 100644
--- a/src/interfaces/libpq/fe-misc.c
+++ b/src/interfaces/libpq/fe-misc.c
@@ -484,9 +484,6 @@ pqCheckInBufferSpace(size_t bytes_needed, PGconn *conn)
  * msg_type is the message type byte, or 0 for a message without type byte
  * (only startup messages have no type byte)
  *
- * force_len forces the message to have a length word; otherwise, we add
- * a length word if protocol 3.
- *
  * Returns 0 on success, EOF on error
  *
  * The idea here is that we construct the message in conn->outBuffer,
@@ -497,12 +494,11 @@ pqCheckInBufferSpace(size_t bytes_needed, PGconn *conn)
  *
  * The state variable conn->outMsgStart points to the incomplete message's
  * length word: it is either outCount or outCount+1 depending on whether
- * there is a type byte.  If we are sending a message without length word
- * (pre protocol 3.0 only), then outMsgStart is -1.  The state variable
- * conn->outMsgEnd is the end of the data collected so far.
+ * there is a type byte.  The state variable conn->outMsgEnd is the end of
+ * the data collected so far.
  */
 int
-pqPutMsgStart(char msg_type, bool force_len, PGconn *conn)
+pqPutMsgStart(char msg_type, PGconn *conn)
 {
 	int			lenPos;
 	int			endPos;
@@ -514,14 +510,9 @@ pqPutMsgStart(char msg_type, bool force_len, PGconn *conn)
 		endPos = conn->outCount;
 
 	/* do we want a length word? */
-	if (force_len || PG_PROTOCOL_MAJOR(conn->pversion) >= 3)
-	{
-		lenPos = endPos;
-		/* allow room for message length */
-		endPos += 4;
-	}
-	else
-		lenPos = -1;
+	lenPos = endPos;
+	/* allow room for message length */
+	endPos += 4;
 
 	/* make sure there is room for message header */
 	if (pqCheckOutBufferSpace(endPos, conn))
diff --git a/src/interfaces/libpq/fe-protocol2.c b/src/interfaces/libpq/fe-protocol2.c
deleted file mode 100644
index 6efa53d8b7..0000000000
--- a/src/interfaces/libpq/fe-protocol2.c
+++ /dev/null
@@ -1,1610 +0,0 @@
-/*-------------------------------------------------------------------------
- *
- * fe-protocol2.c
- *	  functions that are specific to frontend/backend protocol version 2
- *
- * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
- * Portions Copyright (c) 1994, Regents of the University of California
- *
- *
- * IDENTIFICATION
- *	  src/interfaces/libpq/fe-protocol2.c
- *
- *-------------------------------------------------------------------------
- */
-#include "postgres_fe.h"
-
-#include <ctype.h>
-#include <fcntl.h>
-
-#ifdef WIN32
-#include "win32.h"
-#else
-#include <unistd.h>
-#ifdef HAVE_NETINET_TCP_H
-#include <netinet/tcp.h>
-#endif
-#endif
-
-#include "libpq-fe.h"
-#include "libpq-int.h"
-#include "port/pg_bswap.h"
-
-static int	getRowDescriptions(PGconn *conn);
-static int	getAnotherTuple(PGconn *conn, bool binary);
-static int	pqGetErrorNotice2(PGconn *conn, bool isError);
-static void checkXactStatus(PGconn *conn, const char *cmdTag);
-static int	getNotify(PGconn *conn);
-
-
-/*
- *		pqSetenvPoll
- *
- * Polls the process of passing the values of a standard set of environment
- * variables to the backend.
- */
-PostgresPollingStatusType
-pqSetenvPoll(PGconn *conn)
-{
-	PGresult   *res;
-
-	if (conn == NULL || conn->status == CONNECTION_BAD)
-		return PGRES_POLLING_FAILED;
-
-	/* Check whether there are any data for us */
-	switch (conn->setenv_state)
-	{
-			/* These are reading states */
-		case SETENV_STATE_CLIENT_ENCODING_WAIT:
-		case SETENV_STATE_OPTION_WAIT:
-		case SETENV_STATE_QUERY1_WAIT:
-		case SETENV_STATE_QUERY2_WAIT:
-			{
-				/* Load waiting data */
-				int			n = pqReadData(conn);
-
-				if (n < 0)
-					goto error_return;
-				if (n == 0)
-					return PGRES_POLLING_READING;
-
-				break;
-			}
-
-			/* These are writing states, so we just proceed. */
-		case SETENV_STATE_CLIENT_ENCODING_SEND:
-		case SETENV_STATE_OPTION_SEND:
-		case SETENV_STATE_QUERY1_SEND:
-		case SETENV_STATE_QUERY2_SEND:
-			break;
-
-			/* Should we raise an error if called when not active? */
-		case SETENV_STATE_IDLE:
-			return PGRES_POLLING_OK;
-
-		default:
-			appendPQExpBuffer(&conn->errorMessage,
-							  libpq_gettext("invalid setenv state %c, probably indicative of memory corruption\n"),
-							  conn->setenv_state);
-			goto error_return;
-	}
-
-	/* We will loop here until there is nothing left to do in this call. */
-	for (;;)
-	{
-		switch (conn->setenv_state)
-		{
-				/*
-				 * The _CLIENT_ENCODING_SEND code is slightly different from
-				 * _OPTION_SEND below (e.g., no getenv() call), which is why a
-				 * different state is used.
-				 */
-			case SETENV_STATE_CLIENT_ENCODING_SEND:
-				{
-					char		setQuery[100];	/* note length limit in
-												 * sprintf below */
-					const char *val = conn->client_encoding_initial;
-
-					if (val)
-					{
-						if (pg_strcasecmp(val, "default") == 0)
-							sprintf(setQuery, "SET client_encoding = DEFAULT");
-						else
-							sprintf(setQuery, "SET client_encoding = '%.60s'",
-									val);
-#ifdef CONNECTDEBUG
-						fprintf(stderr,
-								"Sending client_encoding with %s\n",
-								setQuery);
-#endif
-						if (!PQsendQuery(conn, setQuery))
-							goto error_return;
-
-						conn->setenv_state = SETENV_STATE_CLIENT_ENCODING_WAIT;
-					}
-					else
-						conn->setenv_state = SETENV_STATE_OPTION_SEND;
-					break;
-				}
-
-			case SETENV_STATE_OPTION_SEND:
-				{
-					/*
-					 * Send SET commands for stuff directed by Environment
-					 * Options.  Note: we assume that SET commands won't start
-					 * transaction blocks, even in a 7.3 server with
-					 * autocommit off.
-					 */
-					char		setQuery[100];	/* note length limit in
-												 * sprintf below */
-
-					if (conn->next_eo->envName)
-					{
-						const char *val;
-
-						if ((val = getenv(conn->next_eo->envName)))
-						{
-							if (pg_strcasecmp(val, "default") == 0)
-								sprintf(setQuery, "SET %s = DEFAULT",
-										conn->next_eo->pgName);
-							else
-								sprintf(setQuery, "SET %s = '%.60s'",
-										conn->next_eo->pgName, val);
-#ifdef CONNECTDEBUG
-							fprintf(stderr,
-									"Use environment variable %s to send %s\n",
-									conn->next_eo->envName, setQuery);
-#endif
-							if (!PQsendQuery(conn, setQuery))
-								goto error_return;
-
-							conn->setenv_state = SETENV_STATE_OPTION_WAIT;
-						}
-						else
-							conn->next_eo++;
-					}
-					else
-					{
-						/* No more options to send, so move on to querying */
-						conn->setenv_state = SETENV_STATE_QUERY1_SEND;
-					}
-					break;
-				}
-
-			case SETENV_STATE_CLIENT_ENCODING_WAIT:
-				{
-					if (PQisBusy(conn))
-						return PGRES_POLLING_READING;
-
-					res = PQgetResult(conn);
-
-					if (res)
-					{
-						if (PQresultStatus(res) != PGRES_COMMAND_OK)
-						{
-							PQclear(res);
-							goto error_return;
-						}
-						PQclear(res);
-						/* Keep reading until PQgetResult returns NULL */
-					}
-					else
-					{
-						/* Query finished, so send the next option */
-						conn->setenv_state = SETENV_STATE_OPTION_SEND;
-					}
-					break;
-				}
-
-			case SETENV_STATE_OPTION_WAIT:
-				{
-					if (PQisBusy(conn))
-						return PGRES_POLLING_READING;
-
-					res = PQgetResult(conn);
-
-					if (res)
-					{
-						if (PQresultStatus(res) != PGRES_COMMAND_OK)
-						{
-							PQclear(res);
-							goto error_return;
-						}
-						PQclear(res);
-						/* Keep reading until PQgetResult returns NULL */
-					}
-					else
-					{
-						/* Query finished, so send the next option */
-						conn->next_eo++;
-						conn->setenv_state = SETENV_STATE_OPTION_SEND;
-					}
-					break;
-				}
-
-			case SETENV_STATE_QUERY1_SEND:
-				{
-					/*
-					 * Issue query to get information we need.  Here we must
-					 * use begin/commit in case autocommit is off by default
-					 * in a 7.3 server.
-					 *
-					 * Note: version() exists in all protocol-2.0-supporting
-					 * backends.  In 7.3 it would be safer to write
-					 * pg_catalog.version(), but we can't do that without
-					 * causing problems on older versions.
-					 */
-					if (!PQsendQuery(conn, "begin; select version(); end"))
-						goto error_return;
-
-					conn->setenv_state = SETENV_STATE_QUERY1_WAIT;
-					return PGRES_POLLING_READING;
-				}
-
-			case SETENV_STATE_QUERY1_WAIT:
-				{
-					if (PQisBusy(conn))
-						return PGRES_POLLING_READING;
-
-					res = PQgetResult(conn);
-
-					if (res)
-					{
-						char	   *val;
-
-						if (PQresultStatus(res) == PGRES_COMMAND_OK)
-						{
-							/* ignore begin/commit command results */
-							PQclear(res);
-							continue;
-						}
-
-						if (PQresultStatus(res) != PGRES_TUPLES_OK ||
-							PQntuples(res) != 1)
-						{
-							PQclear(res);
-							goto error_return;
-						}
-
-						/*
-						 * Extract server version and save as if
-						 * ParameterStatus
-						 */
-						val = PQgetvalue(res, 0, 0);
-						if (val && strncmp(val, "PostgreSQL ", 11) == 0)
-						{
-							char	   *ptr;
-
-							/* strip off PostgreSQL part */
-							val += 11;
-
-							/*
-							 * strip off platform part (scribbles on result,
-							 * naughty naughty)
-							 */
-							ptr = strchr(val, ' ');
-							if (ptr)
-								*ptr = '\0';
-
-							pqSaveParameterStatus(conn, "server_version",
-												  val);
-						}
-
-						PQclear(res);
-						/* Keep reading until PQgetResult returns NULL */
-					}
-					else
-					{
-						/* Query finished, move to next */
-						conn->setenv_state = SETENV_STATE_QUERY2_SEND;
-					}
-					break;
-				}
-
-			case SETENV_STATE_QUERY2_SEND:
-				{
-					const char *query;
-
-					/*
-					 * pg_client_encoding does not exist in pre-7.2 servers.
-					 * So we need to be prepared for an error here.  Do *not*
-					 * start a transaction block, except in 7.3 servers where
-					 * we need to prevent autocommit-off from starting a
-					 * transaction anyway.
-					 */
-					if (conn->sversion >= 70300 &&
-						conn->sversion < 70400)
-						query = "begin; select pg_catalog.pg_client_encoding(); end";
-					else
-						query = "select pg_client_encoding()";
-					if (!PQsendQuery(conn, query))
-						goto error_return;
-
-					conn->setenv_state = SETENV_STATE_QUERY2_WAIT;
-					return PGRES_POLLING_READING;
-				}
-
-			case SETENV_STATE_QUERY2_WAIT:
-				{
-					if (PQisBusy(conn))
-						return PGRES_POLLING_READING;
-
-					res = PQgetResult(conn);
-
-					if (res)
-					{
-						const char *val;
-
-						if (PQresultStatus(res) == PGRES_COMMAND_OK)
-						{
-							/* ignore begin/commit command results */
-							PQclear(res);
-							continue;
-						}
-
-						if (PQresultStatus(res) == PGRES_TUPLES_OK &&
-							PQntuples(res) == 1)
-						{
-							/* Extract client encoding and save it */
-							val = PQgetvalue(res, 0, 0);
-							if (val && *val)	/* null should not happen, but */
-								pqSaveParameterStatus(conn, "client_encoding",
-													  val);
-						}
-						else
-						{
-							/*
-							 * Error: presumably function not available, so
-							 * use PGCLIENTENCODING or SQL_ASCII as the
-							 * fallback.
-							 */
-							val = getenv("PGCLIENTENCODING");
-							if (val && *val)
-								pqSaveParameterStatus(conn, "client_encoding",
-													  val);
-							else
-								pqSaveParameterStatus(conn, "client_encoding",
-													  "SQL_ASCII");
-						}
-
-						PQclear(res);
-						/* Keep reading until PQgetResult returns NULL */
-					}
-					else
-					{
-						/* Query finished, so we're done */
-						conn->setenv_state = SETENV_STATE_IDLE;
-						return PGRES_POLLING_OK;
-					}
-					break;
-				}
-
-			default:
-				appendPQExpBuffer(&conn->errorMessage,
-								  libpq_gettext("invalid state %c, "
-												"probably indicative of memory corruption\n"),
-								  conn->setenv_state);
-				goto error_return;
-		}
-	}
-
-	/* Unreachable */
-
-error_return:
-	conn->setenv_state = SETENV_STATE_IDLE;
-	return PGRES_POLLING_FAILED;
-}
-
-
-/*
- * parseInput: if appropriate, parse input data from backend
- * until input is exhausted or a stopping state is reached.
- * Note that this function will NOT attempt to read more data from the backend.
- */
-void
-pqParseInput2(PGconn *conn)
-{
-	char		id;
-
-	/*
-	 * Loop to parse successive complete messages available in the buffer.
-	 */
-	for (;;)
-	{
-		/*
-		 * Quit if in COPY_OUT state: we expect raw data from the server until
-		 * PQendcopy is called.  Don't try to parse it according to the normal
-		 * protocol.  (This is bogus.  The data lines ought to be part of the
-		 * protocol and have identifying leading characters.)
-		 */
-		if (conn->asyncStatus == PGASYNC_COPY_OUT)
-			return;
-
-		/*
-		 * OK to try to read a message type code.
-		 */
-		conn->inCursor = conn->inStart;
-		if (pqGetc(&id, conn))
-			return;
-
-		/*
-		 * NOTIFY and NOTICE messages can happen in any state besides COPY
-		 * OUT; always process them right away.
-		 *
-		 * Most other messages should only be processed while in BUSY state.
-		 * (In particular, in READY state we hold off further parsing until
-		 * the application collects the current PGresult.)
-		 *
-		 * However, if the state is IDLE then we got trouble; we need to deal
-		 * with the unexpected message somehow.
-		 */
-		if (id == 'A')
-		{
-			if (getNotify(conn))
-				return;
-		}
-		else if (id == 'N')
-		{
-			if (pqGetErrorNotice2(conn, false))
-				return;
-		}
-		else if (conn->asyncStatus != PGASYNC_BUSY)
-		{
-			/* If not IDLE state, just wait ... */
-			if (conn->asyncStatus != PGASYNC_IDLE)
-				return;
-
-			/*
-			 * Unexpected message in IDLE state; need to recover somehow.
-			 * ERROR messages are displayed using the notice processor;
-			 * anything else is just dropped on the floor after displaying a
-			 * suitable warning notice.  (An ERROR is very possibly the
-			 * backend telling us why it is about to close the connection, so
-			 * we don't want to just discard it...)
-			 */
-			if (id == 'E')
-			{
-				if (pqGetErrorNotice2(conn, false /* treat as notice */ ))
-					return;
-			}
-			else
-			{
-				pqInternalNotice(&conn->noticeHooks,
-								 "message type 0x%02x arrived from server while idle",
-								 id);
-				/* Discard the unexpected message; good idea?? */
-				conn->inStart = conn->inEnd;
-				break;
-			}
-		}
-		else
-		{
-			/*
-			 * In BUSY state, we can process everything.
-			 */
-			switch (id)
-			{
-				case 'C':		/* command complete */
-					if (pqGets(&conn->workBuffer, conn))
-						return;
-					if (conn->result == NULL)
-					{
-						conn->result = PQmakeEmptyPGresult(conn,
-														   PGRES_COMMAND_OK);
-						if (!conn->result)
-						{
-							appendPQExpBufferStr(&conn->errorMessage,
-												 libpq_gettext("out of memory"));
-							pqSaveErrorResult(conn);
-						}
-					}
-					if (conn->result)
-					{
-						strlcpy(conn->result->cmdStatus, conn->workBuffer.data,
-								CMDSTATUS_LEN);
-					}
-					checkXactStatus(conn, conn->workBuffer.data);
-					conn->asyncStatus = PGASYNC_READY;
-					break;
-				case 'E':		/* error return */
-					if (pqGetErrorNotice2(conn, true))
-						return;
-					conn->asyncStatus = PGASYNC_READY;
-					break;
-				case 'Z':		/* backend is ready for new query */
-					conn->asyncStatus = PGASYNC_IDLE;
-					break;
-				case 'I':		/* empty query */
-					/* read and throw away the closing '\0' */
-					if (pqGetc(&id, conn))
-						return;
-					if (id != '\0')
-						pqInternalNotice(&conn->noticeHooks,
-										 "unexpected character %c following empty query response (\"I\" message)",
-										 id);
-					if (conn->result == NULL)
-					{
-						conn->result = PQmakeEmptyPGresult(conn,
-														   PGRES_EMPTY_QUERY);
-						if (!conn->result)
-						{
-							appendPQExpBufferStr(&conn->errorMessage,
-												 libpq_gettext("out of memory"));
-							pqSaveErrorResult(conn);
-						}
-					}
-					conn->asyncStatus = PGASYNC_READY;
-					break;
-				case 'K':		/* secret key data from the backend */
-
-					/*
-					 * This is expected only during backend startup, but it's
-					 * just as easy to handle it as part of the main loop.
-					 * Save the data and continue processing.
-					 */
-					if (pqGetInt(&(conn->be_pid), 4, conn))
-						return;
-					if (pqGetInt(&(conn->be_key), 4, conn))
-						return;
-					break;
-				case 'P':		/* synchronous (normal) portal */
-					if (pqGets(&conn->workBuffer, conn))
-						return;
-					/* We pretty much ignore this message type... */
-					break;
-				case 'T':		/* row descriptions (start of query results) */
-					if (conn->result == NULL)
-					{
-						/* First 'T' in a query sequence */
-						if (getRowDescriptions(conn))
-							return;
-						/* getRowDescriptions() moves inStart itself */
-						continue;
-					}
-					else
-					{
-						/*
-						 * A new 'T' message is treated as the start of
-						 * another PGresult.  (It is not clear that this is
-						 * really possible with the current backend.) We stop
-						 * parsing until the application accepts the current
-						 * result.
-						 */
-						conn->asyncStatus = PGASYNC_READY;
-						return;
-					}
-					break;
-				case 'D':		/* ASCII data tuple */
-					if (conn->result != NULL)
-					{
-						/* Read another tuple of a normal query response */
-						if (getAnotherTuple(conn, false))
-							return;
-						/* getAnotherTuple() moves inStart itself */
-						continue;
-					}
-					else
-					{
-						pqInternalNotice(&conn->noticeHooks,
-										 "server sent data (\"D\" message) without prior row description (\"T\" message)");
-						/* Discard the unexpected message; good idea?? */
-						conn->inStart = conn->inEnd;
-						return;
-					}
-					break;
-				case 'B':		/* Binary data tuple */
-					if (conn->result != NULL)
-					{
-						/* Read another tuple of a normal query response */
-						if (getAnotherTuple(conn, true))
-							return;
-						/* getAnotherTuple() moves inStart itself */
-						continue;
-					}
-					else
-					{
-						pqInternalNotice(&conn->noticeHooks,
-										 "server sent binary data (\"B\" message) without prior row description (\"T\" message)");
-						/* Discard the unexpected message; good idea?? */
-						conn->inStart = conn->inEnd;
-						return;
-					}
-					break;
-				case 'G':		/* Start Copy In */
-					conn->asyncStatus = PGASYNC_COPY_IN;
-					break;
-				case 'H':		/* Start Copy Out */
-					conn->asyncStatus = PGASYNC_COPY_OUT;
-					break;
-
-					/*
-					 * Don't need to process CopyBothResponse here because it
-					 * never arrives from the server during protocol 2.0.
-					 */
-				default:
-					appendPQExpBuffer(&conn->errorMessage,
-									  libpq_gettext("unexpected response from server; first received character was \"%c\"\n"),
-									  id);
-					/* build an error result holding the error message */
-					pqSaveErrorResult(conn);
-					/* Discard the unexpected message; good idea?? */
-					conn->inStart = conn->inEnd;
-					conn->asyncStatus = PGASYNC_READY;
-					return;
-			}					/* switch on protocol character */
-		}
-		/* Successfully consumed this message */
-		conn->inStart = conn->inCursor;
-	}
-}
-
-/*
- * parseInput subroutine to read a 'T' (row descriptions) message.
- * We build a PGresult structure containing the attribute data.
- * Returns: 0 if completed message, EOF if error or not enough data
- * received yet.
- *
- * Note that if we run out of data, we have to suspend and reprocess
- * the message after more data is received.  Otherwise, conn->inStart
- * must get advanced past the processed data.
- */
-static int
-getRowDescriptions(PGconn *conn)
-{
-	PGresult   *result;
-	int			nfields;
-	const char *errmsg;
-	int			i;
-
-	result = PQmakeEmptyPGresult(conn, PGRES_TUPLES_OK);
-	if (!result)
-	{
-		errmsg = NULL;			/* means "out of memory", see below */
-		goto advance_and_error;
-	}
-
-	/* parseInput already read the 'T' label. */
-	/* the next two bytes are the number of fields	*/
-	if (pqGetInt(&(result->numAttributes), 2, conn))
-		goto EOFexit;
-	nfields = result->numAttributes;
-
-	/* allocate space for the attribute descriptors */
-	if (nfields > 0)
-	{
-		result->attDescs = (PGresAttDesc *)
-			pqResultAlloc(result, nfields * sizeof(PGresAttDesc), true);
-		if (!result->attDescs)
-		{
-			errmsg = NULL;		/* means "out of memory", see below */
-			goto advance_and_error;
-		}
-		MemSet(result->attDescs, 0, nfields * sizeof(PGresAttDesc));
-	}
-
-	/* get type info */
-	for (i = 0; i < nfields; i++)
-	{
-		int			typid;
-		int			typlen;
-		int			atttypmod;
-
-		if (pqGets(&conn->workBuffer, conn) ||
-			pqGetInt(&typid, 4, conn) ||
-			pqGetInt(&typlen, 2, conn) ||
-			pqGetInt(&atttypmod, 4, conn))
-			goto EOFexit;
-
-		/*
-		 * Since pqGetInt treats 2-byte integers as unsigned, we need to
-		 * coerce the result to signed form.
-		 */
-		typlen = (int) ((int16) typlen);
-
-		result->attDescs[i].name = pqResultStrdup(result,
-												  conn->workBuffer.data);
-		if (!result->attDescs[i].name)
-		{
-			errmsg = NULL;		/* means "out of memory", see below */
-			goto advance_and_error;
-		}
-		result->attDescs[i].tableid = 0;
-		result->attDescs[i].columnid = 0;
-		result->attDescs[i].format = 0;
-		result->attDescs[i].typid = typid;
-		result->attDescs[i].typlen = typlen;
-		result->attDescs[i].atttypmod = atttypmod;
-	}
-
-	/* Success! */
-	conn->result = result;
-
-	/* Advance inStart to show that the "T" message has been processed. */
-	conn->inStart = conn->inCursor;
-
-	/*
-	 * We could perform additional setup for the new result set here, but for
-	 * now there's nothing else to do.
-	 */
-
-	/* And we're done. */
-	return 0;
-
-advance_and_error:
-
-	/*
-	 * Discard the failed message.  Unfortunately we don't know for sure where
-	 * the end is, so just throw away everything in the input buffer. This is
-	 * not very desirable but it's the best we can do in protocol v2.
-	 */
-	conn->inStart = conn->inEnd;
-
-	/*
-	 * Replace partially constructed result with an error result. First
-	 * discard the old result to try to win back some memory.
-	 */
-	pqClearAsyncResult(conn);
-
-	/*
-	 * If preceding code didn't provide an error message, assume "out of
-	 * memory" was meant.  The advantage of having this special case is that
-	 * freeing the old result first greatly improves the odds that gettext()
-	 * will succeed in providing a translation.
-	 */
-	if (!errmsg)
-		errmsg = libpq_gettext("out of memory for query result");
-
-	appendPQExpBuffer(&conn->errorMessage, "%s\n", errmsg);
-
-	/*
-	 * XXX: if PQmakeEmptyPGresult() fails, there's probably not much we can
-	 * do to recover...
-	 */
-	conn->result = PQmakeEmptyPGresult(conn, PGRES_FATAL_ERROR);
-	conn->asyncStatus = PGASYNC_READY;
-
-EOFexit:
-	if (result && result != conn->result)
-		PQclear(result);
-	return EOF;
-}
-
-/*
- * parseInput subroutine to read a 'B' or 'D' (row data) message.
- * We fill rowbuf with column pointers and then call the row processor.
- * Returns: 0 if completed message, EOF if error or not enough data
- * received yet.
- *
- * Note that if we run out of data, we have to suspend and reprocess
- * the message after more data is received.  Otherwise, conn->inStart
- * must get advanced past the processed data.
- */
-static int
-getAnotherTuple(PGconn *conn, bool binary)
-{
-	PGresult   *result = conn->result;
-	int			nfields = result->numAttributes;
-	const char *errmsg;
-	PGdataValue *rowbuf;
-
-	/* the backend sends us a bitmap of which attributes are null */
-	char		std_bitmap[64]; /* used unless it doesn't fit */
-	char	   *bitmap = std_bitmap;
-	int			i;
-	size_t		nbytes;			/* the number of bytes in bitmap  */
-	char		bmap;			/* One byte of the bitmap */
-	int			bitmap_index;	/* Its index */
-	int			bitcnt;			/* number of bits examined in current byte */
-	int			vlen;			/* length of the current field value */
-
-	/* Resize row buffer if needed */
-	rowbuf = conn->rowBuf;
-	if (nfields > conn->rowBufLen)
-	{
-		rowbuf = (PGdataValue *) realloc(rowbuf,
-										 nfields * sizeof(PGdataValue));
-		if (!rowbuf)
-		{
-			errmsg = NULL;		/* means "out of memory", see below */
-			goto advance_and_error;
-		}
-		conn->rowBuf = rowbuf;
-		conn->rowBufLen = nfields;
-	}
-
-	/* Save format specifier */
-	result->binary = binary;
-
-	/*
-	 * If it's binary, fix the column format indicators.  We assume the
-	 * backend will consistently send either B or D, not a mix.
-	 */
-	if (binary)
-	{
-		for (i = 0; i < nfields; i++)
-			result->attDescs[i].format = 1;
-	}
-
-	/* Get the null-value bitmap */
-	nbytes = (nfields + BITS_PER_BYTE - 1) / BITS_PER_BYTE;
-	/* malloc() only for unusually large field counts... */
-	if (nbytes > sizeof(std_bitmap))
-	{
-		bitmap = (char *) malloc(nbytes);
-		if (!bitmap)
-		{
-			errmsg = NULL;		/* means "out of memory", see below */
-			goto advance_and_error;
-		}
-	}
-
-	if (pqGetnchar(bitmap, nbytes, conn))
-		goto EOFexit;
-
-	/* Scan the fields */
-	bitmap_index = 0;
-	bmap = bitmap[bitmap_index];
-	bitcnt = 0;
-
-	for (i = 0; i < nfields; i++)
-	{
-		/* get the value length */
-		if (!(bmap & 0200))
-			vlen = NULL_LEN;
-		else if (pqGetInt(&vlen, 4, conn))
-			goto EOFexit;
-		else
-		{
-			if (!binary)
-				vlen = vlen - 4;
-			if (vlen < 0)
-				vlen = 0;
-		}
-		rowbuf[i].len = vlen;
-
-		/*
-		 * rowbuf[i].value always points to the next address in the data
-		 * buffer even if the value is NULL.  This allows row processors to
-		 * estimate data sizes more easily.
-		 */
-		rowbuf[i].value = conn->inBuffer + conn->inCursor;
-
-		/* Skip over the data value */
-		if (vlen > 0)
-		{
-			if (pqSkipnchar(vlen, conn))
-				goto EOFexit;
-		}
-
-		/* advance the bitmap stuff */
-		bitcnt++;
-		if (bitcnt == BITS_PER_BYTE)
-		{
-			bitmap_index++;
-			bmap = bitmap[bitmap_index];
-			bitcnt = 0;
-		}
-		else
-			bmap <<= 1;
-	}
-
-	/* Release bitmap now if we allocated it */
-	if (bitmap != std_bitmap)
-		free(bitmap);
-	bitmap = NULL;
-
-	/* Advance inStart to show that the "D" message has been processed. */
-	conn->inStart = conn->inCursor;
-
-	/* Process the collected row */
-	errmsg = NULL;
-	if (pqRowProcessor(conn, &errmsg))
-		return 0;				/* normal, successful exit */
-
-	goto set_error_result;		/* pqRowProcessor failed, report it */
-
-advance_and_error:
-
-	/*
-	 * Discard the failed message.  Unfortunately we don't know for sure where
-	 * the end is, so just throw away everything in the input buffer. This is
-	 * not very desirable but it's the best we can do in protocol v2.
-	 */
-	conn->inStart = conn->inEnd;
-
-set_error_result:
-
-	/*
-	 * Replace partially constructed result with an error result. First
-	 * discard the old result to try to win back some memory.
-	 */
-	pqClearAsyncResult(conn);
-
-	/*
-	 * If preceding code didn't provide an error message, assume "out of
-	 * memory" was meant.  The advantage of having this special case is that
-	 * freeing the old result first greatly improves the odds that gettext()
-	 * will succeed in providing a translation.
-	 */
-	if (!errmsg)
-		errmsg = libpq_gettext("out of memory for query result");
-
-	appendPQExpBuffer(&conn->errorMessage, "%s\n", errmsg);
-
-	/*
-	 * XXX: if PQmakeEmptyPGresult() fails, there's probably not much we can
-	 * do to recover...
-	 */
-	conn->result = PQmakeEmptyPGresult(conn, PGRES_FATAL_ERROR);
-	conn->asyncStatus = PGASYNC_READY;
-
-EOFexit:
-	if (bitmap != NULL && bitmap != std_bitmap)
-		free(bitmap);
-	return EOF;
-}
-
-
-/*
- * Attempt to read an Error or Notice response message.
- * This is possible in several places, so we break it out as a subroutine.
- * Entry: 'E' or 'N' message type has already been consumed.
- * Exit: returns 0 if successfully consumed message.
- *		 returns EOF if not enough data.
- */
-static int
-pqGetErrorNotice2(PGconn *conn, bool isError)
-{
-	PGresult   *res = NULL;
-	PQExpBufferData workBuf;
-	char	   *startp;
-	char	   *splitp;
-
-	/*
-	 * If this is an error message, pre-emptively clear any incomplete query
-	 * result we may have.  We'd just throw it away below anyway, and
-	 * releasing it before collecting the error might avoid out-of-memory.
-	 */
-	if (isError)
-		pqClearAsyncResult(conn);
-
-	/*
-	 * Since the message might be pretty long, we create a temporary
-	 * PQExpBuffer rather than using conn->workBuffer.  workBuffer is intended
-	 * for stuff that is expected to be short.
-	 */
-	initPQExpBuffer(&workBuf);
-	if (pqGets(&workBuf, conn))
-		goto failure;
-
-	/*
-	 * Make a PGresult to hold the message.  We temporarily lie about the
-	 * result status, so that PQmakeEmptyPGresult doesn't uselessly copy
-	 * conn->errorMessage.
-	 *
-	 * NB: This allocation can fail, if you run out of memory. The rest of the
-	 * function handles that gracefully, and we still try to set the error
-	 * message as the connection's error message.
-	 */
-	res = PQmakeEmptyPGresult(conn, PGRES_EMPTY_QUERY);
-	if (res)
-	{
-		res->resultStatus = isError ? PGRES_FATAL_ERROR : PGRES_NONFATAL_ERROR;
-		res->errMsg = pqResultStrdup(res, workBuf.data);
-	}
-
-	/*
-	 * Break the message into fields.  We can't do very much here, but we can
-	 * split the severity code off, and remove trailing newlines. Also, we use
-	 * the heuristic that the primary message extends only to the first
-	 * newline --- anything after that is detail message.  (In some cases it'd
-	 * be better classed as hint, but we can hardly be expected to guess that
-	 * here.)
-	 */
-	while (workBuf.len > 0 && workBuf.data[workBuf.len - 1] == '\n')
-		workBuf.data[--workBuf.len] = '\0';
-	splitp = strstr(workBuf.data, ":  ");
-	if (splitp)
-	{
-		/* what comes before the colon is severity */
-		*splitp = '\0';
-		pqSaveMessageField(res, PG_DIAG_SEVERITY, workBuf.data);
-		startp = splitp + 3;
-	}
-	else
-	{
-		/* can't find a colon?  oh well... */
-		startp = workBuf.data;
-	}
-	splitp = strchr(startp, '\n');
-	if (splitp)
-	{
-		/* what comes before the newline is primary message */
-		*splitp++ = '\0';
-		pqSaveMessageField(res, PG_DIAG_MESSAGE_PRIMARY, startp);
-		/* the rest is detail; strip any leading whitespace */
-		while (*splitp && isspace((unsigned char) *splitp))
-			splitp++;
-		pqSaveMessageField(res, PG_DIAG_MESSAGE_DETAIL, splitp);
-	}
-	else
-	{
-		/* single-line message, so all primary */
-		pqSaveMessageField(res, PG_DIAG_MESSAGE_PRIMARY, startp);
-	}
-
-	/*
-	 * Either save error as current async result, or just emit the notice.
-	 * Also, if it's an error and we were in a transaction block, assume the
-	 * server has now gone to error-in-transaction state.
-	 */
-	if (isError)
-	{
-		pqClearAsyncResult(conn);	/* redundant, but be safe */
-		conn->result = res;
-		if (res && !PQExpBufferDataBroken(workBuf) && res->errMsg)
-			appendPQExpBufferStr(&conn->errorMessage, res->errMsg);
-		else
-			appendPQExpBufferStr(&conn->errorMessage,
-								 libpq_gettext("out of memory"));
-		if (conn->xactStatus == PQTRANS_INTRANS)
-			conn->xactStatus = PQTRANS_INERROR;
-	}
-	else
-	{
-		if (res)
-		{
-			if (res->noticeHooks.noticeRec != NULL)
-				res->noticeHooks.noticeRec(res->noticeHooks.noticeRecArg, res);
-			PQclear(res);
-		}
-	}
-
-	termPQExpBuffer(&workBuf);
-	return 0;
-
-failure:
-	if (res)
-		PQclear(res);
-	termPQExpBuffer(&workBuf);
-	return EOF;
-}
-
-/*
- * checkXactStatus - attempt to track transaction-block status of server
- *
- * This is called each time we receive a command-complete message.  By
- * watching for messages from BEGIN/COMMIT/ROLLBACK commands, we can do
- * a passable job of tracking the server's xact status.  BUT: this does
- * not work at all on 7.3 servers with AUTOCOMMIT OFF.  (Man, was that
- * feature ever a mistake.)  Caveat user.
- *
- * The tags known here are all those used as far back as 7.0; is it worth
- * adding those from even-older servers?
- */
-static void
-checkXactStatus(PGconn *conn, const char *cmdTag)
-{
-	if (strcmp(cmdTag, "BEGIN") == 0)
-		conn->xactStatus = PQTRANS_INTRANS;
-	else if (strcmp(cmdTag, "COMMIT") == 0)
-		conn->xactStatus = PQTRANS_IDLE;
-	else if (strcmp(cmdTag, "ROLLBACK") == 0)
-		conn->xactStatus = PQTRANS_IDLE;
-	else if (strcmp(cmdTag, "START TRANSACTION") == 0)	/* 7.3 only */
-		conn->xactStatus = PQTRANS_INTRANS;
-
-	/*
-	 * Normally we get into INERROR state by detecting an Error message.
-	 * However, if we see one of these tags then we know for sure the server
-	 * is in abort state ...
-	 */
-	else if (strcmp(cmdTag, "*ABORT STATE*") == 0)	/* pre-7.3 only */
-		conn->xactStatus = PQTRANS_INERROR;
-}
-
-/*
- * Attempt to read a Notify response message.
- * This is possible in several places, so we break it out as a subroutine.
- * Entry: 'A' message type and length have already been consumed.
- * Exit: returns 0 if successfully consumed Notify message.
- *		 returns EOF if not enough data.
- */
-static int
-getNotify(PGconn *conn)
-{
-	int			be_pid;
-	int			nmlen;
-	PGnotify   *newNotify;
-
-	if (pqGetInt(&be_pid, 4, conn))
-		return EOF;
-	if (pqGets(&conn->workBuffer, conn))
-		return EOF;
-
-	/*
-	 * Store the relation name right after the PQnotify structure so it can
-	 * all be freed at once.  We don't use NAMEDATALEN because we don't want
-	 * to tie this interface to a specific server name length.
-	 */
-	nmlen = strlen(conn->workBuffer.data);
-	newNotify = (PGnotify *) malloc(sizeof(PGnotify) + nmlen + 1);
-	if (newNotify)
-	{
-		newNotify->relname = (char *) newNotify + sizeof(PGnotify);
-		strcpy(newNotify->relname, conn->workBuffer.data);
-		/* fake up an empty-string extra field */
-		newNotify->extra = newNotify->relname + nmlen;
-		newNotify->be_pid = be_pid;
-		newNotify->next = NULL;
-		if (conn->notifyTail)
-			conn->notifyTail->next = newNotify;
-		else
-			conn->notifyHead = newNotify;
-		conn->notifyTail = newNotify;
-	}
-
-	return 0;
-}
-
-
-/*
- * PQgetCopyData - read a row of data from the backend during COPY OUT
- *
- * If successful, sets *buffer to point to a malloc'd row of data, and
- * returns row length (always > 0) as result.
- * Returns 0 if no row available yet (only possible if async is true),
- * -1 if end of copy (consult PQgetResult), or -2 if error (consult
- * PQerrorMessage).
- */
-int
-pqGetCopyData2(PGconn *conn, char **buffer, int async)
-{
-	bool		found;
-	int			msgLength;
-
-	for (;;)
-	{
-		/*
-		 * Do we have a complete line of data?
-		 */
-		conn->inCursor = conn->inStart;
-		found = false;
-		while (conn->inCursor < conn->inEnd)
-		{
-			char		c = conn->inBuffer[conn->inCursor++];
-
-			if (c == '\n')
-			{
-				found = true;
-				break;
-			}
-		}
-		if (!found)
-			goto nodata;
-		msgLength = conn->inCursor - conn->inStart;
-
-		/*
-		 * If it's the end-of-data marker, consume it, exit COPY_OUT mode, and
-		 * let caller read status with PQgetResult().
-		 */
-		if (msgLength == 3 &&
-			strncmp(&conn->inBuffer[conn->inStart], "\\.\n", 3) == 0)
-		{
-			conn->inStart = conn->inCursor;
-			conn->asyncStatus = PGASYNC_BUSY;
-			return -1;
-		}
-
-		/*
-		 * Pass the line back to the caller.
-		 */
-		*buffer = (char *) malloc(msgLength + 1);
-		if (*buffer == NULL)
-		{
-			appendPQExpBufferStr(&conn->errorMessage,
-								 libpq_gettext("out of memory\n"));
-			return -2;
-		}
-		memcpy(*buffer, &conn->inBuffer[conn->inStart], msgLength);
-		(*buffer)[msgLength] = '\0';	/* Add terminating null */
-
-		/* Mark message consumed */
-		conn->inStart = conn->inCursor;
-
-		return msgLength;
-
-nodata:
-		/* Don't block if async read requested */
-		if (async)
-			return 0;
-		/* Need to load more data */
-		if (pqWait(true, false, conn) ||
-			pqReadData(conn) < 0)
-			return -2;
-	}
-}
-
-
-/*
- * PQgetline - gets a newline-terminated string from the backend.
- *
- * See fe-exec.c for documentation.
- */
-int
-pqGetline2(PGconn *conn, char *s, int maxlen)
-{
-	int			result = 1;		/* return value if buffer overflows */
-
-	if (conn->sock == PGINVALID_SOCKET ||
-		conn->asyncStatus != PGASYNC_COPY_OUT)
-	{
-		*s = '\0';
-		return EOF;
-	}
-
-	/*
-	 * Since this is a purely synchronous routine, we don't bother to maintain
-	 * conn->inCursor; there is no need to back up.
-	 */
-	while (maxlen > 1)
-	{
-		if (conn->inStart < conn->inEnd)
-		{
-			char		c = conn->inBuffer[conn->inStart++];
-
-			if (c == '\n')
-			{
-				result = 0;		/* success exit */
-				break;
-			}
-			*s++ = c;
-			maxlen--;
-		}
-		else
-		{
-			/* need to load more data */
-			if (pqWait(true, false, conn) ||
-				pqReadData(conn) < 0)
-			{
-				result = EOF;
-				break;
-			}
-		}
-	}
-	*s = '\0';
-
-	return result;
-}
-
-/*
- * PQgetlineAsync - gets a COPY data row without blocking.
- *
- * See fe-exec.c for documentation.
- */
-int
-pqGetlineAsync2(PGconn *conn, char *buffer, int bufsize)
-{
-	int			avail;
-
-	if (conn->asyncStatus != PGASYNC_COPY_OUT)
-		return -1;				/* we are not doing a copy... */
-
-	/*
-	 * Move data from libpq's buffer to the caller's. We want to accept data
-	 * only in units of whole lines, not partial lines.  This ensures that we
-	 * can recognize the terminator line "\\.\n".  (Otherwise, if it happened
-	 * to cross a packet/buffer boundary, we might hand the first one or two
-	 * characters off to the caller, which we shouldn't.)
-	 */
-
-	conn->inCursor = conn->inStart;
-
-	avail = bufsize;
-	while (avail > 0 && conn->inCursor < conn->inEnd)
-	{
-		char		c = conn->inBuffer[conn->inCursor++];
-
-		*buffer++ = c;
-		--avail;
-		if (c == '\n')
-		{
-			/* Got a complete line; mark the data removed from libpq */
-			conn->inStart = conn->inCursor;
-			/* Is it the endmarker line? */
-			if (bufsize - avail == 3 && buffer[-3] == '\\' && buffer[-2] == '.')
-				return -1;
-			/* No, return the data line to the caller */
-			return bufsize - avail;
-		}
-	}
-
-	/*
-	 * We don't have a complete line. We'd prefer to leave it in libpq's
-	 * buffer until the rest arrives, but there is a special case: what if the
-	 * line is longer than the buffer the caller is offering us?  In that case
-	 * we'd better hand over a partial line, else we'd get into an infinite
-	 * loop. Do this in a way that ensures we can't misrecognize a terminator
-	 * line later: leave last 3 characters in libpq buffer.
-	 */
-	if (avail == 0 && bufsize > 3)
-	{
-		conn->inStart = conn->inCursor - 3;
-		return bufsize - 3;
-	}
-	return 0;
-}
-
-/*
- * PQendcopy
- *
- * See fe-exec.c for documentation.
- */
-int
-pqEndcopy2(PGconn *conn)
-{
-	PGresult   *result;
-
-	if (conn->asyncStatus != PGASYNC_COPY_IN &&
-		conn->asyncStatus != PGASYNC_COPY_OUT)
-	{
-		appendPQExpBufferStr(&conn->errorMessage,
-							 libpq_gettext("no COPY in progress\n"));
-		return 1;
-	}
-
-	/*
-	 * make sure no data is waiting to be sent, abort if we are non-blocking
-	 * and the flush fails
-	 */
-	if (pqFlush(conn) && pqIsnonblocking(conn))
-		return 1;
-
-	/* non blocking connections may have to abort at this point. */
-	if (pqIsnonblocking(conn) && PQisBusy(conn))
-		return 1;
-
-	/* Return to active duty */
-	conn->asyncStatus = PGASYNC_BUSY;
-
-	/* Wait for the completion response */
-	result = PQgetResult(conn);
-
-	/* Expecting a successful result */
-	if (result && result->resultStatus == PGRES_COMMAND_OK)
-	{
-		PQclear(result);
-		return 0;
-	}
-
-	/*
-	 * Trouble. For backwards-compatibility reasons, we issue the error
-	 * message as if it were a notice (would be nice to get rid of this
-	 * silliness, but too many apps probably don't handle errors from
-	 * PQendcopy reasonably).  Note that the app can still obtain the error
-	 * status from the PGconn object.
-	 */
-	if (conn->errorMessage.len > 0)
-	{
-		/* We have to strip the trailing newline ... pain in neck... */
-		char		svLast = conn->errorMessage.data[conn->errorMessage.len - 1];
-
-		if (svLast == '\n')
-			conn->errorMessage.data[conn->errorMessage.len - 1] = '\0';
-		pqInternalNotice(&conn->noticeHooks, "%s", conn->errorMessage.data);
-		conn->errorMessage.data[conn->errorMessage.len - 1] = svLast;
-	}
-
-	PQclear(result);
-
-	/*
-	 * The worst case is that we've lost sync with the backend entirely due to
-	 * application screwup of the copy in/out protocol. To recover, reset the
-	 * connection (talk about using a sledgehammer...)
-	 */
-	pqInternalNotice(&conn->noticeHooks,
-					 "lost synchronization with server, resetting connection");
-
-	/*
-	 * Users doing non-blocking connections need to handle the reset
-	 * themselves, they'll need to check the connection status if we return an
-	 * error.
-	 */
-	if (pqIsnonblocking(conn))
-		PQresetStart(conn);
-	else
-		PQreset(conn);
-
-	return 1;
-}
-
-
-/*
- * PQfn - Send a function call to the POSTGRES backend.
- *
- * See fe-exec.c for documentation.
- */
-PGresult *
-pqFunctionCall2(PGconn *conn, Oid fnid,
-				int *result_buf, int *actual_result_len,
-				int result_is_int,
-				const PQArgBlock *args, int nargs)
-{
-	bool		needInput = false;
-	ExecStatusType status = PGRES_FATAL_ERROR;
-	char		id;
-	int			i;
-
-	/* PQfn already validated connection state */
-
-	if (pqPutMsgStart('F', false, conn) < 0 ||	/* function call msg */
-		pqPuts(" ", conn) < 0 ||	/* dummy string */
-		pqPutInt(fnid, 4, conn) != 0 || /* function id */
-		pqPutInt(nargs, 4, conn) != 0)	/* # of args */
-	{
-		/* error message should be set up already */
-		return NULL;
-	}
-
-	for (i = 0; i < nargs; ++i)
-	{							/* len.int4 + contents	   */
-		if (pqPutInt(args[i].len, 4, conn))
-			return NULL;
-
-		if (args[i].isint)
-		{
-			if (pqPutInt(args[i].u.integer, 4, conn))
-				return NULL;
-		}
-		else
-		{
-			if (pqPutnchar((char *) args[i].u.ptr, args[i].len, conn))
-				return NULL;
-		}
-	}
-
-	if (pqPutMsgEnd(conn) < 0 ||
-		pqFlush(conn))
-		return NULL;
-
-	for (;;)
-	{
-		if (needInput)
-		{
-			/* Wait for some data to arrive (or for the channel to close) */
-			if (pqWait(true, false, conn) ||
-				pqReadData(conn) < 0)
-				break;
-		}
-
-		/*
-		 * Scan the message. If we run out of data, loop around to try again.
-		 */
-		conn->inCursor = conn->inStart;
-		needInput = true;
-
-		if (pqGetc(&id, conn))
-			continue;
-
-		/*
-		 * We should see V or E response to the command, but might get N
-		 * and/or A notices first. We also need to swallow the final Z before
-		 * returning.
-		 */
-		switch (id)
-		{
-			case 'V':			/* function result */
-				if (pqGetc(&id, conn))
-					continue;
-				if (id == 'G')
-				{
-					/* function returned nonempty value */
-					if (pqGetInt(actual_result_len, 4, conn))
-						continue;
-					if (result_is_int)
-					{
-						if (pqGetInt(result_buf, 4, conn))
-							continue;
-					}
-					else
-					{
-						if (pqGetnchar((char *) result_buf,
-									   *actual_result_len,
-									   conn))
-							continue;
-					}
-					if (pqGetc(&id, conn))	/* get the last '0' */
-						continue;
-				}
-				if (id == '0')
-				{
-					/* correctly finished function result message */
-					status = PGRES_COMMAND_OK;
-				}
-				else
-				{
-					/* The backend violates the protocol. */
-					appendPQExpBuffer(&conn->errorMessage,
-									  libpq_gettext("protocol error: id=0x%x\n"),
-									  id);
-					pqSaveErrorResult(conn);
-					conn->inStart = conn->inCursor;
-					return pqPrepareAsyncResult(conn);
-				}
-				break;
-			case 'E':			/* error return */
-				if (pqGetErrorNotice2(conn, true))
-					continue;
-				status = PGRES_FATAL_ERROR;
-				break;
-			case 'A':			/* notify message */
-				/* handle notify and go back to processing return values */
-				if (getNotify(conn))
-					continue;
-				break;
-			case 'N':			/* notice */
-				/* handle notice and go back to processing return values */
-				if (pqGetErrorNotice2(conn, false))
-					continue;
-				break;
-			case 'Z':			/* backend is ready for new query */
-				/* consume the message and exit */
-				conn->inStart = conn->inCursor;
-				/* if we saved a result object (probably an error), use it */
-				if (conn->result)
-					return pqPrepareAsyncResult(conn);
-				return PQmakeEmptyPGresult(conn, status);
-			default:
-				/* The backend violates the protocol. */
-				appendPQExpBuffer(&conn->errorMessage,
-								  libpq_gettext("protocol error: id=0x%x\n"),
-								  id);
-				pqSaveErrorResult(conn);
-				conn->inStart = conn->inCursor;
-				return pqPrepareAsyncResult(conn);
-		}
-		/* Completed this message, keep going */
-		conn->inStart = conn->inCursor;
-		needInput = false;
-	}
-
-	/*
-	 * We fall out of the loop only upon failing to read data.
-	 * conn->errorMessage has been set by pqWait or pqReadData. We want to
-	 * append it to any already-received error message.
-	 */
-	pqSaveErrorResult(conn);
-	return pqPrepareAsyncResult(conn);
-}
-
-
-/*
- * Construct startup packet
- *
- * Returns a malloc'd packet buffer, or NULL if out of memory
- */
-char *
-pqBuildStartupPacket2(PGconn *conn, int *packetlen,
-					  const PQEnvironmentOption *options)
-{
-	StartupPacket *startpacket;
-
-	*packetlen = sizeof(StartupPacket);
-	startpacket = (StartupPacket *) malloc(sizeof(StartupPacket));
-	if (!startpacket)
-		return NULL;
-
-	MemSet(startpacket, 0, sizeof(StartupPacket));
-
-	startpacket->protoVersion = pg_hton32(conn->pversion);
-
-	/* strncpy is safe here: postmaster will handle full fields correctly */
-	strncpy(startpacket->user, conn->pguser, SM_USER);
-	strncpy(startpacket->database, conn->dbName, SM_DATABASE);
-	strncpy(startpacket->tty, conn->pgtty, SM_TTY);
-
-	if (conn->pgoptions)
-		strncpy(startpacket->options, conn->pgoptions, SM_OPTIONS);
-
-	return (char *) startpacket;
-}
diff --git a/src/interfaces/libpq/fe-protocol3.c b/src/interfaces/libpq/fe-protocol3.c
index e4ee9d69d2..2ca8c057b9 100644
--- a/src/interfaces/libpq/fe-protocol3.c
+++ b/src/interfaces/libpq/fe-protocol3.c
@@ -1843,7 +1843,7 @@ pqEndcopy3(PGconn *conn)
 	if (conn->asyncStatus == PGASYNC_COPY_IN ||
 		conn->asyncStatus == PGASYNC_COPY_BOTH)
 	{
-		if (pqPutMsgStart('c', false, conn) < 0 ||
+		if (pqPutMsgStart('c', conn) < 0 ||
 			pqPutMsgEnd(conn) < 0)
 			return 1;
 
@@ -1853,7 +1853,7 @@ pqEndcopy3(PGconn *conn)
 		 */
 		if (conn->queryclass != PGQUERY_SIMPLE)
 		{
-			if (pqPutMsgStart('S', false, conn) < 0 ||
+			if (pqPutMsgStart('S', conn) < 0 ||
 				pqPutMsgEnd(conn) < 0)
 				return 1;
 		}
@@ -1933,7 +1933,7 @@ pqFunctionCall3(PGconn *conn, Oid fnid,
 
 	/* PQfn already validated connection state */
 
-	if (pqPutMsgStart('F', false, conn) < 0 ||	/* function call msg */
+	if (pqPutMsgStart('F', conn) < 0 || /* function call msg */
 		pqPutInt(fnid, 4, conn) < 0 ||	/* function id */
 		pqPutInt(1, 2, conn) < 0 || /* # of format codes */
 		pqPutInt(1, 2, conn) < 0 || /* format code: BINARY */
diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h
index 47a098b4b9..fa9b62a844 100644
--- a/src/interfaces/libpq/libpq-fe.h
+++ b/src/interfaces/libpq/libpq-fe.h
@@ -60,7 +60,7 @@ typedef enum
 									 * postmaster.        */
 	CONNECTION_AUTH_OK,			/* Received authentication; waiting for
 								 * backend startup. */
-	CONNECTION_SETENV,			/* Negotiating environment. */
+	CONNECTION_SETENV,			/* This state is no longer used. */
 	CONNECTION_SSL_STARTUP,		/* Negotiating SSL. */
 	CONNECTION_NEEDED,			/* Internal state: connect() needed */
 	CONNECTION_CHECK_WRITABLE,	/* Checking if session is read-write. */
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index 0c9e95f1a7..8d51e6ed9f 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -252,22 +252,6 @@ typedef enum
 	PG_BOOL_NO					/* No (false) */
 } PGTernaryBool;
 
-/* PGSetenvStatusType defines the state of the pqSetenv state machine */
-
-/* (this is used only for 2.0-protocol connections) */
-typedef enum
-{
-	SETENV_STATE_CLIENT_ENCODING_SEND,	/* About to send an Environment Option */
-	SETENV_STATE_CLIENT_ENCODING_WAIT,	/* Waiting for above send to complete */
-	SETENV_STATE_OPTION_SEND,	/* About to send an Environment Option */
-	SETENV_STATE_OPTION_WAIT,	/* Waiting for above send to complete */
-	SETENV_STATE_QUERY1_SEND,	/* About to send a status query */
-	SETENV_STATE_QUERY1_WAIT,	/* Waiting for query to complete */
-	SETENV_STATE_QUERY2_SEND,	/* About to send a status query */
-	SETENV_STATE_QUERY2_WAIT,	/* Waiting for query to complete */
-	SETENV_STATE_IDLE
-} PGSetenvStatusType;
-
 /* Typedef for the EnvironmentOptions[] array */
 typedef struct PQEnvironmentOption
 {
@@ -446,8 +430,6 @@ struct pg_conn
 	struct addrinfo *addrlist;	/* list of addresses for current connhost */
 	struct addrinfo *addr_cur;	/* the one currently being tried */
 	int			addrlist_family;	/* needed to know how to free addrlist */
-	PGSetenvStatusType setenv_state;	/* for 2.0 protocol only */
-	const PQEnvironmentOption *next_eo;
 	bool		send_appname;	/* okay to send application_name? */
 
 	/* Miscellaneous stuff */
@@ -639,22 +621,6 @@ extern void pqSaveParameterStatus(PGconn *conn, const char *name,
 extern int	pqRowProcessor(PGconn *conn, const char **errmsgp);
 extern int	PQsendQueryContinue(PGconn *conn, const char *query);
 
-/* === in fe-protocol2.c === */
-
-extern PostgresPollingStatusType pqSetenvPoll(PGconn *conn);
-
-extern char *pqBuildStartupPacket2(PGconn *conn, int *packetlen,
-								   const PQEnvironmentOption *options);
-extern void pqParseInput2(PGconn *conn);
-extern int	pqGetCopyData2(PGconn *conn, char **buffer, int async);
-extern int	pqGetline2(PGconn *conn, char *s, int maxlen);
-extern int	pqGetlineAsync2(PGconn *conn, char *buffer, int bufsize);
-extern int	pqEndcopy2(PGconn *conn);
-extern PGresult *pqFunctionCall2(PGconn *conn, Oid fnid,
-								 int *result_buf, int *actual_result_len,
-								 int result_is_int,
-								 const PQArgBlock *args, int nargs);
-
 /* === in fe-protocol3.c === */
 
 extern char *pqBuildStartupPacket3(PGconn *conn, int *packetlen,
@@ -691,7 +657,7 @@ extern int	pqSkipnchar(size_t len, PGconn *conn);
 extern int	pqPutnchar(const char *s, size_t len, PGconn *conn);
 extern int	pqGetInt(int *result, size_t bytes, PGconn *conn);
 extern int	pqPutInt(int value, size_t bytes, PGconn *conn);
-extern int	pqPutMsgStart(char msg_type, bool force_len, PGconn *conn);
+extern int	pqPutMsgStart(char msg_type, PGconn *conn);
 extern int	pqPutMsgEnd(PGconn *conn);
 extern int	pqReadData(PGconn *conn);
 extern int	pqFlush(PGconn *conn);
#16Tom Lane
tgl@sss.pgh.pa.us
In reply to: Tom Lane (#15)
Re: Removing support for COPY FROM STDIN in protocol version 2

I wrote:

I concur that 0001 attached is committable. I have not looked at
your 0002, though.

Oh ... grepping discovered one more loose end: mention of fe-protocol2.c
has to be removed from src/interfaces/libpq/nls.mk.

regards, tom lane

#17Heikki Linnakangas
hlinnaka@iki.fi
In reply to: Tom Lane (#15)
Re: Removing support for COPY FROM STDIN in protocol version 2

On 04/03/2021 01:32, Tom Lane wrote:

Patched psql, trying to connect to a 7.3 server, reports this:

$ psql -h ...
psql: error: connection to server at "sss2" (192.168.1.3), port 5432 failed: FATAL: unsupported frontend protocol

$

Conversely, 7.3 psql trying to connect to a patched server reports:

$ psql -h ...
psql: FATAL: unsupported frontend protocol 2.0: server supports 3.0 to 3.0

$

I'm not sure where the extra newlines are coming from, and it seems
unlikely to be worth worrying over. This behavior is good enough for me.

fe-connect.c appends a newline for any errors in pre-3.0 format:

/*
* The postmaster typically won't end its message with a
* newline, so add one to conform to libpq conventions.
*/
appendPQExpBufferChar(&conn->errorMessage, '\n');

That comment is wrong. The postmaster *does* end all its error messages
with a newline. This changed in commit 9b4bfbdc2c in 7.2. Before that,
postmaster had its own function, PacketSendError(), to send error
messages, and it did not append a newline. Commit 9b4bfbdc2 changed
postmaster to use elog(...) like everyone else, and elog(...) has always
appended a newline. So I think this extra newline that libpq adds is
needed if you try to connect to PostgreSQL 7.1 or earlier. I couldn't
commpile a 7.1 server to verify this, though.

I changed that code in libpq to check if the message already has a
newline, and only append one if it doesn't. This fixes the extra newline
when connecting with new libpq to a 7.3 server (and in the fork failure
message).

I concur that 0001 attached is committable. I have not looked at
your 0002, though.

Removed the entry from nls.mk, and pushed 0001. Thanks!

- Heikki

#18Tom Lane
tgl@sss.pgh.pa.us
In reply to: Heikki Linnakangas (#17)
Re: Removing support for COPY FROM STDIN in protocol version 2

Heikki Linnakangas <hlinnaka@iki.fi> writes:

On 04/03/2021 01:32, Tom Lane wrote:

I'm not sure where the extra newlines are coming from, and it seems
unlikely to be worth worrying over. This behavior is good enough for me.

fe-connect.c appends a newline for any errors in pre-3.0 format:

/*
* The postmaster typically won't end its message with a
* newline, so add one to conform to libpq conventions.
*/
appendPQExpBufferChar(&conn->errorMessage, '\n');

That comment is wrong. The postmaster *does* end all its error messages
with a newline. This changed in commit 9b4bfbdc2c in 7.2.

Ah-hah, and the bit you show here came in with 2af360ed1, in 7.0.
I'm surprised though that we didn't notice that the newline was now
usually redundant. This was a commonly taken code path until 7.4.

Anyway, your fix seems fine ... I wonder if we should back-patch it?

regards, tom lane

#19Tom Lane
tgl@sss.pgh.pa.us
In reply to: Heikki Linnakangas (#17)
Re: Removing support for COPY FROM STDIN in protocol version 2

Heikki Linnakangas <hlinnaka@iki.fi> writes:

I concur that 0001 attached is committable. I have not looked at
your 0002, though.

Removed the entry from nls.mk, and pushed 0001. Thanks!

It seems that buildfarm member walleye doesn't like this.
Since nothing else is complaining, I confess bafflement
as to why. walleye seems to be our only active mingw animal,
so maybe there's a platform dependency somewhere ... but
how would (mostly) removal of code expose that?

regards, tom lane

#20Heikki Linnakangas
hlinnaka@iki.fi
In reply to: Tom Lane (#19)
Re: Removing support for COPY FROM STDIN in protocol version 2

On 04/03/2021 22:04, Tom Lane wrote:

Heikki Linnakangas <hlinnaka@iki.fi> writes:

I concur that 0001 attached is committable. I have not looked at
your 0002, though.

Removed the entry from nls.mk, and pushed 0001. Thanks!

It seems that buildfarm member walleye doesn't like this.
Since nothing else is complaining, I confess bafflement
as to why. walleye seems to be our only active mingw animal,
so maybe there's a platform dependency somewhere ... but
how would (mostly) removal of code expose that?

Strange indeed. The commands that are crashing seem far detached from
any FE/BE protocol handling, and I don't see any other pattern either:

2021-03-04 05:08:45.953 EST [4080:94] DETAIL: Failed process was
running: copy (insert into copydml_test default values) to stdout;

2021-03-04 05:09:22.690 EST [4080:100] DETAIL: Failed process was
running: CREATE INDEX CONCURRENTLY concur_index7 ON concur_heap(f1);

2021-03-04 05:09:33.546 EST [4080:106] DETAIL: Failed process was
running: ANALYZE vaccluster;

2021-03-04 05:09:42.452 EST [4080:112] DETAIL: Failed process was
running: FETCH BACKWARD 1 FROM foo24;

2021-03-04 05:10:06.874 EST [4080:118] DETAIL: Failed process was
running: REFRESH MATERIALIZED VIEW CONCURRENTLY mvtest_tvmm;

2021-03-04 05:12:23.890 EST [4080:125] DETAIL: Failed process was
running: CREATE SUBSCRIPTION regress_testsub CONNECTION 'testconn'
PUBLICATION testpub;

2021-03-04 05:15:46.421 EST [4080:297] DETAIL: Failed process was
running: INSERT INTO xmltest VALUES (3, '<wrong');

Dare I suggest a compiler bug? gcc 8.1 isn't the fully up-to-date,
although I don't know if there's a newer gcc available on this platform.
Joseph, any chance we could see a backtrace or some other details from
those crashes?

'drongo' just reported linker errors:

postgres.def : error LNK2001: unresolved external symbol
GetOldFunctionMessage
[c:\\prog\\bf\\root\\HEAD\\pgsql.build\\postgres.vcxproj]
postgres.def : error LNK2001: unresolved external symbol errfunction
[c:\\prog\\bf\\root\\HEAD\\pgsql.build\\postgres.vcxproj]
postgres.def : error LNK2001: unresolved external symbol pq_getstring
[c:\\prog\\bf\\root\\HEAD\\pgsql.build\\postgres.vcxproj]
postgres.def : error LNK2001: unresolved external symbol pq_putbytes
[c:\\prog\\bf\\root\\HEAD\\pgsql.build\\postgres.vcxproj]
Release/postgres/postgres.lib : fatal error LNK1120: 4 unresolved
externals [c:\\prog\\bf\\root\\HEAD\\pgsql.build\\postgres.vcxproj]
Done Building Project
"c:\\prog\\bf\\root\\HEAD\\pgsql.build\\postgres.vcxproj" (default
targets) -- FAILED.

Looks like it wasn't a clean build, those functions and all the callers
were removed by the patch. That's a separate issue than on 'walleye' -
unless that was also not a completely clean build?

- Heikki

#21Andrew Dunstan
andrew@dunslane.net
In reply to: Heikki Linnakangas (#20)
Re: Removing support for COPY FROM STDIN in protocol version 2

On 3/4/21 3:55 PM, Heikki Linnakangas wrote:

'drongo' just reported linker errors:

postgres.def : error LNK2001: unresolved external symbol
GetOldFunctionMessage
[c:\\prog\\bf\\root\\HEAD\\pgsql.build\\postgres.vcxproj]
postgres.def : error LNK2001: unresolved external symbol errfunction
[c:\\prog\\bf\\root\\HEAD\\pgsql.build\\postgres.vcxproj]
postgres.def : error LNK2001: unresolved external symbol pq_getstring
[c:\\prog\\bf\\root\\HEAD\\pgsql.build\\postgres.vcxproj]
postgres.def : error LNK2001: unresolved external symbol pq_putbytes
[c:\\prog\\bf\\root\\HEAD\\pgsql.build\\postgres.vcxproj]
Release/postgres/postgres.lib : fatal error LNK1120: 4 unresolved
externals [c:\\prog\\bf\\root\\HEAD\\pgsql.build\\postgres.vcxproj]
Done Building Project
"c:\\prog\\bf\\root\\HEAD\\pgsql.build\\postgres.vcxproj" (default
targets) -- FAILED.

Looks like it wasn't a clean build, those functions and all the
callers were removed by the patch. That's a separate issue than on
'walleye' - unless that was also not a completely clean build?

Yes, pilot error :-)(

It's rerunning and should report clean shortly

cheers

andrew

--
Andrew Dunstan
EDB: https://www.enterprisedb.com

#22Tom Lane
tgl@sss.pgh.pa.us
In reply to: Heikki Linnakangas (#20)
Re: Removing support for COPY FROM STDIN in protocol version 2

Heikki Linnakangas <hlinnaka@iki.fi> writes:

Joseph, any chance we could see a backtrace or some other details from
those crashes?

+1

'drongo' just reported linker errors:
postgres.def : error LNK2001: unresolved external symbol
GetOldFunctionMessage
[c:\\prog\\bf\\root\\HEAD\\pgsql.build\\postgres.vcxproj]
postgres.def : error LNK2001: unresolved external symbol errfunction
[c:\\prog\\bf\\root\\HEAD\\pgsql.build\\postgres.vcxproj]
postgres.def : error LNK2001: unresolved external symbol pq_getstring
[c:\\prog\\bf\\root\\HEAD\\pgsql.build\\postgres.vcxproj]
postgres.def : error LNK2001: unresolved external symbol pq_putbytes
[c:\\prog\\bf\\root\\HEAD\\pgsql.build\\postgres.vcxproj]
Release/postgres/postgres.lib : fatal error LNK1120: 4 unresolved
externals [c:\\prog\\bf\\root\\HEAD\\pgsql.build\\postgres.vcxproj]
Done Building Project
"c:\\prog\\bf\\root\\HEAD\\pgsql.build\\postgres.vcxproj" (default
targets) -- FAILED.

As far as that goes, I think suspicion has to fall on this:

Not re-generating POSTGRES.DEF, file already exists.

which gendef.pl prints if it thinks the def file is newer than
all the inputs. So either drongo had some kind of clock skew
issue, or that bit of logic in gendef.pl has some unobvious bug.

(I say "had" because I see the next run went fine.)

regards, tom lane