From b2ed5c9525294984732f99f3409812316a3385e7 Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Thu, 15 Sep 2022 19:36:41 +0200
Subject: [PATCH] remove PQsendQuery support in pipeline mode

---
 doc/src/sgml/libpq.sgml                       |   9 +-
 src/interfaces/libpq/fe-exec.c                | 111 +++---------------
 src/interfaces/libpq/fe-protocol3.c           |  18 +--
 .../modules/libpq_pipeline/libpq_pipeline.c   |  19 +--
 .../traces/pipeline_abort.trace               |   6 +-
 .../libpq_pipeline/traces/pipeline_idle.trace |  28 ++---
 6 files changed, 44 insertions(+), 147 deletions(-)

diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index f14216e727..54068c25cb 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -4599,8 +4599,7 @@ int PQsendQuery(PGconn *conn, const char *command);
       </para>
 
       <para>
-       In pipeline mode, command strings containing more than one SQL command
-       are disallowed.
+       In pipeline mode, this function is disallowed.
       </para>
      </listitem>
     </varlistentry>
@@ -5054,6 +5053,7 @@ int PQflush(PGconn *conn);
     <xref linkend="libpq-PQpipelineStatus"/> can be used
     to test whether pipeline mode is active.
     In pipeline mode, only <link linkend="libpq-async">asynchronous operations</link>
+    that utilize the extended query protocol
     are permitted, command strings containing multiple SQL commands are
     disallowed, and so is <literal>COPY</literal>.
     Using synchronous command execution functions
@@ -5065,6 +5065,8 @@ int PQflush(PGconn *conn);
     <function>PQdescribePrepared</function>,
     <function>PQdescribePortal</function>,
     is an error condition.
+    <function>PQsendQuery</function> is
+    also disallowed, because it uses the simple query protocol.
     Once all dispatched commands have had their results processed, and
     the end pipeline result has been consumed, the application may return
     to non-pipelined mode with <xref linkend="libpq-PQexitPipelineMode"/>.
@@ -5093,8 +5095,7 @@ int PQflush(PGconn *conn);
 
     <para>
      After entering pipeline mode, the application dispatches requests using
-     <xref linkend="libpq-PQsendQuery"/>,
-     <xref linkend="libpq-PQsendQueryParams"/>,
+     <xref linkend="libpq-PQsendQueryParams"/>
      or its prepared-query sibling
      <xref linkend="libpq-PQsendQueryPrepared"/>.
      These requests are queued on the client-side until flushed to the server;
diff --git a/src/interfaces/libpq/fe-exec.c b/src/interfaces/libpq/fe-exec.c
index 23ceff7502..7c926ce451 100644
--- a/src/interfaces/libpq/fe-exec.c
+++ b/src/interfaces/libpq/fe-exec.c
@@ -1437,7 +1437,6 @@ static int
 PQsendQueryInternal(PGconn *conn, const char *query, bool newQuery)
 {
 	PGcmdQueueEntry *entry = NULL;
-	PGcmdQueueEntry *entry2 = NULL;
 
 	if (!PQsendQueryStart(conn, newQuery))
 		return 0;
@@ -1450,103 +1449,47 @@ PQsendQueryInternal(PGconn *conn, const char *query, bool newQuery)
 		return 0;
 	}
 
+	if (conn->pipelineStatus != PQ_PIPELINE_OFF)
+	{
+		appendPQExpBufferStr(&conn->errorMessage,
+							 libpq_gettext("cannot use PQsendQuery in pipeline mode"));
+		return 0;
+	}
+
 	entry = pqAllocCmdQueueEntry(conn);
 	if (entry == NULL)
 		return 0;				/* error msg already set */
-	if (conn->pipelineStatus != PQ_PIPELINE_OFF)
-	{
-		entry2 = pqAllocCmdQueueEntry(conn);
-		if (entry2 == NULL)
-			goto sendFailed;
-	}
 
 	/* Send the query message(s) */
-	if (conn->pipelineStatus == PQ_PIPELINE_OFF)
+	/* construct the outgoing Query message */
+	if (pqPutMsgStart('Q', conn) < 0 ||
+		pqPuts(query, conn) < 0 ||
+		pqPutMsgEnd(conn) < 0)
 	{
-		/* construct the outgoing Query message */
-		if (pqPutMsgStart('Q', conn) < 0 ||
-			pqPuts(query, conn) < 0 ||
-			pqPutMsgEnd(conn) < 0)
-		{
-			/* error message should be set up already */
-			pqRecycleCmdQueueEntry(conn, entry);
-			return 0;
-		}
-
-		/* remember we are using simple query protocol */
-		entry->queryclass = PGQUERY_SIMPLE;
-		/* and remember the query text too, if possible */
-		entry->query = strdup(query);
+		/* error message should be set up already */
+		pqRecycleCmdQueueEntry(conn, entry);
+		return 0;
 	}
-	else
-	{
-		/*
-		 * In pipeline mode we cannot use the simple protocol, so we send
-		 * Parse, Bind, Describe Portal, Execute, Close Portal (with the
-		 * unnamed portal).
-		 */
-		if (pqPutMsgStart('P', conn) < 0 ||
-			pqPuts("", conn) < 0 ||
-			pqPuts(query, conn) < 0 ||
-			pqPutInt(0, 2, conn) < 0 ||
-			pqPutMsgEnd(conn) < 0)
-			goto sendFailed;
-		if (pqPutMsgStart('B', conn) < 0 ||
-			pqPuts("", conn) < 0 ||
-			pqPuts("", conn) < 0 ||
-			pqPutInt(0, 2, conn) < 0 ||
-			pqPutInt(0, 2, conn) < 0 ||
-			pqPutInt(0, 2, conn) < 0 ||
-			pqPutMsgEnd(conn) < 0)
-			goto sendFailed;
-		if (pqPutMsgStart('D', conn) < 0 ||
-			pqPutc('P', conn) < 0 ||
-			pqPuts("", conn) < 0 ||
-			pqPutMsgEnd(conn) < 0)
-			goto sendFailed;
-		if (pqPutMsgStart('E', conn) < 0 ||
-			pqPuts("", conn) < 0 ||
-			pqPutInt(0, 4, conn) < 0 ||
-			pqPutMsgEnd(conn) < 0)
-			goto sendFailed;
-		if (pqPutMsgStart('C', conn) < 0 ||
-			pqPutc('P', conn) < 0 ||
-			pqPuts("", conn) < 0 ||
-			pqPutMsgEnd(conn) < 0)
-			goto sendFailed;
 
-		entry->queryclass = PGQUERY_EXTENDED;
-		entry->query = strdup(query);
-	}
+	/* remember we are using simple query protocol */
+	entry->queryclass = PGQUERY_SIMPLE;
+	/* and remember the query text too, if possible */
+	entry->query = strdup(query);
 
 	/*
 	 * Give the data a push.  In nonblock mode, don't complain if we're unable
 	 * to send it all; PQgetResult() will do any additional flushing needed.
 	 */
-	if (pqPipelineFlush(conn) < 0)
+	if (pqFlush(conn) < 0)
 		goto sendFailed;
 
 	/* OK, it's launched! */
 	pqAppendCmdQueueEntry(conn, entry);
 
-	/*
-	 * When pipeline mode is in use, we need a second entry in the command
-	 * queue to represent Close Portal message.  This allows us later to wait
-	 * for the CloseComplete message to be received before getting in IDLE
-	 * state.
-	 */
-	if (conn->pipelineStatus != PQ_PIPELINE_OFF)
-	{
-		entry2->queryclass = PGQUERY_CLOSE;
-		entry2->query = NULL;
-		pqAppendCmdQueueEntry(conn, entry2);
-	}
-
 	return 1;
 
 sendFailed:
 	pqRecycleCmdQueueEntry(conn, entry);
-	pqRecycleCmdQueueEntry(conn, entry2);
 	/* error message should be set up already */
 	return 0;
 }
@@ -2250,22 +2193,6 @@ PQgetResult(PGconn *conn)
 			break;
 	}
 
-	/* If the next command we expect is CLOSE, read and consume it */
-	if (conn->asyncStatus == PGASYNC_PIPELINE_IDLE &&
-		conn->cmd_queue_head &&
-		conn->cmd_queue_head->queryclass == PGQUERY_CLOSE)
-	{
-		if (res && res->resultStatus != PGRES_FATAL_ERROR)
-		{
-			conn->asyncStatus = PGASYNC_BUSY;
-			parseInput(conn);
-			conn->asyncStatus = PGASYNC_PIPELINE_IDLE;
-		}
-		else
-			/* we won't ever see the Close */
-			pqCommandQueueAdvance(conn);
-	}
-
 	/* Time to fire PGEVT_RESULTCREATE events, if there are any */
 	if (res && res->nEvents > 0)
 		(void) PQfireResultCreateEvents(conn, res);
diff --git a/src/interfaces/libpq/fe-protocol3.c b/src/interfaces/libpq/fe-protocol3.c
index f267dfd33c..0d60e8c5c0 100644
--- a/src/interfaces/libpq/fe-protocol3.c
+++ b/src/interfaces/libpq/fe-protocol3.c
@@ -284,24 +284,8 @@ pqParseInput3(PGconn *conn)
 					}
 					break;
 				case '2':		/* Bind Complete */
-					/* Nothing to do for this message type */
-					break;
 				case '3':		/* Close Complete */
-					/*
-					 * If we get CloseComplete when waiting for it, consume
-					 * the queue element and keep going.  A result is not
-					 * expected from this message; it is just there so that
-					 * we know to wait for it when PQsendQuery is used in
-					 * pipeline mode, before going in IDLE state.  Failing to
-					 * do this makes us receive CloseComplete when IDLE, which
-					 * creates problems.
-					 */
-					if (conn->cmd_queue_head &&
-						conn->cmd_queue_head->queryclass == PGQUERY_CLOSE)
-					{
-						pqCommandQueueAdvance(conn);
-					}
-
+					/* Nothing to do for these message types */
 					break;
 				case 'S':		/* parameter status */
 					if (getParameterStatus(conn))
diff --git a/src/test/modules/libpq_pipeline/libpq_pipeline.c b/src/test/modules/libpq_pipeline/libpq_pipeline.c
index 04a56ca505..882b9afdea 100644
--- a/src/test/modules/libpq_pipeline/libpq_pipeline.c
+++ b/src/test/modules/libpq_pipeline/libpq_pipeline.c
@@ -499,7 +499,7 @@ test_pipeline_abort(PGconn *conn)
 				 PQerrorMessage(conn));
 
 	/* Try to send two queries in one command */
-	if (PQsendQuery(conn, "SELECT 1; SELECT 2") != 1)
+	if (PQsendQueryParams(conn, "SELECT 1; SELECT 2", 0, NULL, NULL, NULL, NULL, 0) != 1)
 		pg_fatal("failed to send query: %s", PQerrorMessage(conn));
 	if (PQpipelineSync(conn) != 1)
 		pg_fatal("pipeline sync failed: %s", PQerrorMessage(conn));
@@ -531,7 +531,8 @@ test_pipeline_abort(PGconn *conn)
 	fprintf(stderr, "ok\n");
 
 	/* Test single-row mode with an error partways */
-	if (PQsendQuery(conn, "SELECT 1.0/g FROM generate_series(3, -1, -1) g") != 1)
+	if (PQsendQueryParams(conn, "SELECT 1.0/g FROM generate_series(3, -1, -1) g",
+						  0, NULL, NULL, NULL, NULL, 0) != 1)
 		pg_fatal("failed to send query: %s", PQerrorMessage(conn));
 	if (PQpipelineSync(conn) != 1)
 		pg_fatal("pipeline sync failed: %s", PQerrorMessage(conn));
@@ -999,7 +1000,7 @@ test_pipeline_idle(PGconn *conn)
 	if (PQenterPipelineMode(conn) != 1)
 		pg_fatal("failed to enter pipeline mode: %s", PQerrorMessage(conn));
 
-	if (PQsendQuery(conn, "SELECT 1") != 1)
+	if (PQsendQueryParams(conn, "SELECT 1", 0, NULL, NULL, NULL, NULL, 0) != 1)
 		pg_fatal("failed to send query: %s", PQerrorMessage(conn));
 	PQsendFlushRequest(conn);
 	res = PQgetResult(conn);
@@ -1045,7 +1046,7 @@ test_pipeline_idle(PGconn *conn)
 	 */
 	if (PQenterPipelineMode(conn) != 1)
 		pg_fatal("failed to enter pipeline mode: %s", PQerrorMessage(conn));
-	if (PQsendQuery(conn, "SELECT 1") != 1)
+	if (PQsendQueryParams(conn, "SELECT 1", 0, NULL, NULL, NULL, NULL, 0) != 1)
 		pg_fatal("failed to send query: %s", PQerrorMessage(conn));
 	PQsendFlushRequest(conn);
 	res = PQgetResult(conn);
@@ -1083,7 +1084,7 @@ test_pipeline_idle(PGconn *conn)
 
 	if (PQenterPipelineMode(conn) != 1)
 		pg_fatal("failed to enter pipeline mode: %s", PQerrorMessage(conn));
-	if (PQsendQuery(conn, "SELECT 1") != 1)
+	if (PQsendQueryParams(conn, "SELECT 1", 0, NULL, NULL, NULL, NULL, 0) != 1)
 		pg_fatal("failed to send query: %s", PQerrorMessage(conn));
 	PQsendFlushRequest(conn);
 	res = PQgetResult(conn);
@@ -1093,7 +1094,7 @@ test_pipeline_idle(PGconn *conn)
 	if (PQresultStatus(res) != PGRES_TUPLES_OK)
 		pg_fatal("unexpected result code %s from first pipeline item",
 				 PQresStatus(PQresultStatus(res)));
-	if (PQsendQuery(conn, "SELECT 2") != 1)
+	if (PQsendQueryParams(conn, "SELECT 2", 0, NULL, NULL, NULL, NULL, 0) != 1)
 		pg_fatal("failed to send query: %s", PQerrorMessage(conn));
 	PQsendFlushRequest(conn);
 	/* read terminating null from first query */
@@ -1117,7 +1118,7 @@ test_pipeline_idle(PGconn *conn)
 	/* Try to exit pipeline mode in pipeline-idle state */
 	if (PQenterPipelineMode(conn) != 1)
 		pg_fatal("failed to enter pipeline mode: %s", PQerrorMessage(conn));
-	if (PQsendQuery(conn, "SELECT 1") != 1)
+	if (PQsendQueryParams(conn, "SELECT 1", 0, NULL, NULL, NULL, NULL, 0) != 1)
 		pg_fatal("failed to send query: %s", PQerrorMessage(conn));
 	PQsendFlushRequest(conn);
 	res = PQgetResult(conn);
@@ -1131,7 +1132,7 @@ test_pipeline_idle(PGconn *conn)
 	res = PQgetResult(conn);
 	if (res != NULL)
 		pg_fatal("did not receive terminating NULL");
-	if (PQsendQuery(conn, "SELECT 2") != 1)
+	if (PQsendQueryParams(conn, "SELECT 2", 0, NULL, NULL, NULL, NULL, 0) != 1)
 		pg_fatal("failed to send query: %s", PQerrorMessage(conn));
 	if (PQexitPipelineMode(conn) == 1)
 		pg_fatal("exiting pipeline succeeded when it shouldn't");
@@ -1158,7 +1159,7 @@ test_pipeline_idle(PGconn *conn)
 	/* Have a WARNING in the middle of a resultset */
 	if (PQenterPipelineMode(conn) != 1)
 		pg_fatal("entering pipeline mode failed: %s", PQerrorMessage(conn));
-	if (PQsendQuery(conn, "SELECT pg_catalog.pg_advisory_unlock(1,1)") != 1)
+	if (PQsendQueryParams(conn, "SELECT pg_catalog.pg_advisory_unlock(1,1)", 0, NULL, NULL, NULL, NULL, 0) != 1)
 		pg_fatal("failed to send query: %s", PQerrorMessage(conn));
 	PQsendFlushRequest(conn);
 	res = PQgetResult(conn);
diff --git a/src/test/modules/libpq_pipeline/traces/pipeline_abort.trace b/src/test/modules/libpq_pipeline/traces/pipeline_abort.trace
index 3fce548b99..cf6ccec6b9 100644
--- a/src/test/modules/libpq_pipeline/traces/pipeline_abort.trace
+++ b/src/test/modules/libpq_pipeline/traces/pipeline_abort.trace
@@ -35,18 +35,16 @@ B	4	NoData
 B	15	CommandComplete	 "INSERT 0 1"
 B	5	ReadyForQuery	 I
 F	26	Parse	 "" "SELECT 1; SELECT 2" 0
-F	12	Bind	 "" "" 0 0 0
+F	14	Bind	 "" "" 0 0 1 0
 F	6	Describe	 P ""
 F	9	Execute	 "" 0
-F	6	Close	 P ""
 F	4	Sync
 B	NN	ErrorResponse	 S "ERROR" V "ERROR" C "42601" M "cannot insert multiple commands into a prepared statement" F "SSSS" L "SSSS" R "SSSS" \x00
 B	5	ReadyForQuery	 I
 F	54	Parse	 "" "SELECT 1.0/g FROM generate_series(3, -1, -1) g" 0
-F	12	Bind	 "" "" 0 0 0
+F	14	Bind	 "" "" 0 0 1 0
 F	6	Describe	 P ""
 F	9	Execute	 "" 0
-F	6	Close	 P ""
 F	4	Sync
 B	4	ParseComplete
 B	4	BindComplete
diff --git a/src/test/modules/libpq_pipeline/traces/pipeline_idle.trace b/src/test/modules/libpq_pipeline/traces/pipeline_idle.trace
index 3957ee4dfe..48b78d9d86 100644
--- a/src/test/modules/libpq_pipeline/traces/pipeline_idle.trace
+++ b/src/test/modules/libpq_pipeline/traces/pipeline_idle.trace
@@ -1,87 +1,74 @@
 F	16	Parse	 "" "SELECT 1" 0
-F	12	Bind	 "" "" 0 0 0
+F	14	Bind	 "" "" 0 0 1 0
 F	6	Describe	 P ""
 F	9	Execute	 "" 0
-F	6	Close	 P ""
 F	4	Flush
 B	4	ParseComplete
 B	4	BindComplete
 B	33	RowDescription	 1 "?column?" NNNN 0 NNNN 4 -1 0
 B	11	DataRow	 1 1 '1'
 B	13	CommandComplete	 "SELECT 1"
-B	4	CloseComplete
 F	4	Sync
 B	5	ReadyForQuery	 I
 F	16	Parse	 "" "SELECT 1" 0
-F	12	Bind	 "" "" 0 0 0
+F	14	Bind	 "" "" 0 0 1 0
 F	6	Describe	 P ""
 F	9	Execute	 "" 0
-F	6	Close	 P ""
 F	4	Flush
 B	4	ParseComplete
 B	4	BindComplete
 B	33	RowDescription	 1 "?column?" NNNN 0 NNNN 4 -1 0
 B	11	DataRow	 1 1 '1'
 B	13	CommandComplete	 "SELECT 1"
-B	4	CloseComplete
 F	13	Query	 "SELECT 2"
 B	33	RowDescription	 1 "?column?" NNNN 0 NNNN 4 -1 0
 B	11	DataRow	 1 1 '2'
 B	13	CommandComplete	 "SELECT 1"
 B	5	ReadyForQuery	 I
 F	16	Parse	 "" "SELECT 1" 0
-F	12	Bind	 "" "" 0 0 0
+F	14	Bind	 "" "" 0 0 1 0
 F	6	Describe	 P ""
 F	9	Execute	 "" 0
-F	6	Close	 P ""
 F	4	Flush
 B	4	ParseComplete
 B	4	BindComplete
 B	33	RowDescription	 1 "?column?" NNNN 0 NNNN 4 -1 0
 B	11	DataRow	 1 1 '1'
 B	13	CommandComplete	 "SELECT 1"
-B	4	CloseComplete
 F	16	Parse	 "" "SELECT 2" 0
-F	12	Bind	 "" "" 0 0 0
+F	14	Bind	 "" "" 0 0 1 0
 F	6	Describe	 P ""
 F	9	Execute	 "" 0
-F	6	Close	 P ""
 F	4	Flush
 B	4	ParseComplete
 B	4	BindComplete
 B	33	RowDescription	 1 "?column?" NNNN 0 NNNN 4 -1 0
 B	11	DataRow	 1 1 '2'
 B	13	CommandComplete	 "SELECT 1"
-B	4	CloseComplete
 F	16	Parse	 "" "SELECT 1" 0
-F	12	Bind	 "" "" 0 0 0
+F	14	Bind	 "" "" 0 0 1 0
 F	6	Describe	 P ""
 F	9	Execute	 "" 0
-F	6	Close	 P ""
 F	4	Flush
 B	4	ParseComplete
 B	4	BindComplete
 B	33	RowDescription	 1 "?column?" NNNN 0 NNNN 4 -1 0
 B	11	DataRow	 1 1 '1'
 B	13	CommandComplete	 "SELECT 1"
-B	4	CloseComplete
 F	16	Parse	 "" "SELECT 2" 0
-F	12	Bind	 "" "" 0 0 0
+F	14	Bind	 "" "" 0 0 1 0
 F	6	Describe	 P ""
 F	9	Execute	 "" 0
-F	6	Close	 P ""
 F	4	Flush
 B	4	ParseComplete
 B	4	BindComplete
 B	33	RowDescription	 1 "?column?" NNNN 0 NNNN 4 -1 0
 B	11	DataRow	 1 1 '2'
 B	13	CommandComplete	 "SELECT 1"
-B	4	CloseComplete
 F	49	Parse	 "" "SELECT pg_catalog.pg_advisory_unlock(1,1)" 0
-F	12	Bind	 "" "" 0 0 0
+F	14	Bind	 "" "" 0 0 1 0
 F	6	Describe	 P ""
 F	9	Execute	 "" 0
-F	6	Close	 P ""
 F	4	Flush
 B	4	ParseComplete
 B	4	BindComplete
@@ -89,5 +76,4 @@ B	43	RowDescription	 1 "pg_advisory_unlock" NNNN 0 NNNN 1 -1 0
 B	NN	NoticeResponse	 S "WARNING" V "WARNING" C "01000" M "you don't own a lock of type ExclusiveLock" F "SSSS" L "SSSS" R "SSSS" \x00
 B	11	DataRow	 1 1 'f'
 B	13	CommandComplete	 "SELECT 1"
-B	4	CloseComplete
 F	4	Terminate
-- 
2.30.2

