From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Subject: [PATCH] remove PQsendQuery support in pipeline mode
Date: Thu, 15 Sep 2022 19:36:41 +0200
---
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(-)
inline patch 1: doc/src/sgml/libpq.sgml, src/interfaces/libpq/fe-exec.c, src/interfaces/libpq/fe-protocol3.c and 3 more (+44/-147 lines) download inline-1.patchdiff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgmlindex 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.cindex 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.cindex 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.cindex 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.traceindex 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.traceindex 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
--4dlcx4vwbhjrt47j--
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Reply to all the recipients using the --to and --cc options:
reply via email
To: pgsql-bugs@postgresql.org
Cc: alvherre@alvh.no-ip.org
Subject: Re: [PATCH] remove PQsendQuery support in pipeline mode
In-Reply-To: <no-message-id-29084@localhost>
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
This inbox is served by DDX for PostgreSQL; see mirroring instructions
for how to clone and mirror all data and code used for this inbox