Skip to content

Commit 0032a54

Browse files
committed
Remove PQsendQuery support in pipeline mode
The extended query protocol implementation I added in commit acb7e4e has bugs when used in pipeline mode. Rather than spend more time trying to fix it, remove that code and make the function rely on simple query protocol only, meaning it can no longer be used in pipeline mode. Users can easily change their applications to use PQsendQueryParams instead. We leave PQsendQuery in place for Postgres 14, just in case somebody is using it and has not hit the mentioned bugs; but we should recommend that it not be used. Backpatch to 15. Per bug report from Gabriele Varrazzo. Discussion: https://postgr.es/m/CA+mi_8ZGSQNmW6-mk_iSR4JZB_LJ4ww3suOF+1vGNs3MrLsv4g@mail.gmail.com
1 parent d11a41a commit 0032a54

File tree

4 files changed

+41
-115
lines changed

4 files changed

+41
-115
lines changed

doc/src/sgml/libpq.sgml

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4601,8 +4601,7 @@ int PQsendQuery(PGconn *conn, const char *command);
46014601
</para>
46024602

46034603
<para>
4604-
In pipeline mode, command strings containing more than one SQL command
4605-
are disallowed.
4604+
In pipeline mode, this function is disallowed.
46064605
</para>
46074606
</listitem>
46084607
</varlistentry>
@@ -5056,6 +5055,7 @@ int PQflush(PGconn *conn);
50565055
<xref linkend="libpq-PQpipelineStatus"/> can be used
50575056
to test whether pipeline mode is active.
50585057
In pipeline mode, only <link linkend="libpq-async">asynchronous operations</link>
5058+
that utilize the extended query protocol
50595059
are permitted, command strings containing multiple SQL commands are
50605060
disallowed, and so is <literal>COPY</literal>.
50615061
Using synchronous command execution functions
@@ -5067,6 +5067,8 @@ int PQflush(PGconn *conn);
50675067
<function>PQdescribePrepared</function>,
50685068
<function>PQdescribePortal</function>,
50695069
is an error condition.
5070+
<function>PQsendQuery</function> is
5071+
also disallowed, because it uses the simple query protocol.
50705072
Once all dispatched commands have had their results processed, and
50715073
the end pipeline result has been consumed, the application may return
50725074
to non-pipelined mode with <xref linkend="libpq-PQexitPipelineMode"/>.
@@ -5095,8 +5097,7 @@ int PQflush(PGconn *conn);
50955097

50965098
<para>
50975099
After entering pipeline mode, the application dispatches requests using
5098-
<xref linkend="libpq-PQsendQuery"/>,
5099-
<xref linkend="libpq-PQsendQueryParams"/>,
5100+
<xref linkend="libpq-PQsendQueryParams"/>
51005101
or its prepared-query sibling
51015102
<xref linkend="libpq-PQsendQueryPrepared"/>.
51025103
These requests are queued on the client-side until flushed to the server;

src/interfaces/libpq/fe-exec.c

Lines changed: 23 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -1433,7 +1433,6 @@ static int
14331433
PQsendQueryInternal(PGconn *conn, const char *query, bool newQuery)
14341434
{
14351435
PGcmdQueueEntry *entry = NULL;
1436-
PGcmdQueueEntry *entry2 = NULL;
14371436

14381437
if (!PQsendQueryStart(conn, newQuery))
14391438
return 0;
@@ -1446,103 +1445,48 @@ PQsendQueryInternal(PGconn *conn, const char *query, bool newQuery)
14461445
return 0;
14471446
}
14481447

1449-
entry = pqAllocCmdQueueEntry(conn);
1450-
if (entry == NULL)
1451-
return 0; /* error msg already set */
14521448
if (conn->pipelineStatus != PQ_PIPELINE_OFF)
14531449
{
1454-
entry2 = pqAllocCmdQueueEntry(conn);
1455-
if (entry2 == NULL)
1456-
goto sendFailed;
1450+
appendPQExpBuffer(&conn->errorMessage,
1451+
libpq_gettext("%s not allowed in pipeline mode\n"),
1452+
"PQsendQuery");
1453+
return 0;
14571454
}
14581455

1456+
entry = pqAllocCmdQueueEntry(conn);
1457+
if (entry == NULL)
1458+
return 0; /* error msg already set */
1459+
14591460
/* Send the query message(s) */
1460-
if (conn->pipelineStatus == PQ_PIPELINE_OFF)
1461+
/* construct the outgoing Query message */
1462+
if (pqPutMsgStart('Q', conn) < 0 ||
1463+
pqPuts(query, conn) < 0 ||
1464+
pqPutMsgEnd(conn) < 0)
14611465
{
1462-
/* construct the outgoing Query message */
1463-
if (pqPutMsgStart('Q', conn) < 0 ||
1464-
pqPuts(query, conn) < 0 ||
1465-
pqPutMsgEnd(conn) < 0)
1466-
{
1467-
/* error message should be set up already */
1468-
pqRecycleCmdQueueEntry(conn, entry);
1469-
return 0;
1470-
}
1471-
1472-
/* remember we are using simple query protocol */
1473-
entry->queryclass = PGQUERY_SIMPLE;
1474-
/* and remember the query text too, if possible */
1475-
entry->query = strdup(query);
1466+
/* error message should be set up already */
1467+
pqRecycleCmdQueueEntry(conn, entry);
1468+
return 0;
14761469
}
1477-
else
1478-
{
1479-
/*
1480-
* In pipeline mode we cannot use the simple protocol, so we send
1481-
* Parse, Bind, Describe Portal, Execute, Close Portal (with the
1482-
* unnamed portal).
1483-
*/
1484-
if (pqPutMsgStart('P', conn) < 0 ||
1485-
pqPuts("", conn) < 0 ||
1486-
pqPuts(query, conn) < 0 ||
1487-
pqPutInt(0, 2, conn) < 0 ||
1488-
pqPutMsgEnd(conn) < 0)
1489-
goto sendFailed;
1490-
if (pqPutMsgStart('B', conn) < 0 ||
1491-
pqPuts("", conn) < 0 ||
1492-
pqPuts("", conn) < 0 ||
1493-
pqPutInt(0, 2, conn) < 0 ||
1494-
pqPutInt(0, 2, conn) < 0 ||
1495-
pqPutInt(0, 2, conn) < 0 ||
1496-
pqPutMsgEnd(conn) < 0)
1497-
goto sendFailed;
1498-
if (pqPutMsgStart('D', conn) < 0 ||
1499-
pqPutc('P', conn) < 0 ||
1500-
pqPuts("", conn) < 0 ||
1501-
pqPutMsgEnd(conn) < 0)
1502-
goto sendFailed;
1503-
if (pqPutMsgStart('E', conn) < 0 ||
1504-
pqPuts("", conn) < 0 ||
1505-
pqPutInt(0, 4, conn) < 0 ||
1506-
pqPutMsgEnd(conn) < 0)
1507-
goto sendFailed;
1508-
if (pqPutMsgStart('C', conn) < 0 ||
1509-
pqPutc('P', conn) < 0 ||
1510-
pqPuts("", conn) < 0 ||
1511-
pqPutMsgEnd(conn) < 0)
1512-
goto sendFailed;
15131470

1514-
entry->queryclass = PGQUERY_EXTENDED;
1515-
entry->query = strdup(query);
1516-
}
1471+
/* remember we are using simple query protocol */
1472+
entry->queryclass = PGQUERY_SIMPLE;
1473+
/* and remember the query text too, if possible */
1474+
entry->query = strdup(query);
15171475

15181476
/*
15191477
* Give the data a push. In nonblock mode, don't complain if we're unable
15201478
* to send it all; PQgetResult() will do any additional flushing needed.
15211479
*/
1522-
if (pqPipelineFlush(conn) < 0)
1480+
if (pqFlush(conn) < 0)
15231481
goto sendFailed;
15241482

15251483
/* OK, it's launched! */
15261484
pqAppendCmdQueueEntry(conn, entry);
15271485

1528-
/*
1529-
* When pipeline mode is in use, we need a second entry in the command
1530-
* queue to represent Close Portal message. This allows us later to wait
1531-
* for the CloseComplete message to be received before getting in IDLE
1532-
* state.
1533-
*/
1534-
if (conn->pipelineStatus != PQ_PIPELINE_OFF)
1535-
{
1536-
entry2->queryclass = PGQUERY_CLOSE;
1537-
entry2->query = NULL;
1538-
pqAppendCmdQueueEntry(conn, entry2);
1539-
}
1540-
15411486
return 1;
15421487

15431488
sendFailed:
15441489
pqRecycleCmdQueueEntry(conn, entry);
1545-
pqRecycleCmdQueueEntry(conn, entry2);
15461490
/* error message should be set up already */
15471491
return 0;
15481492
}
@@ -2246,22 +2190,6 @@ PQgetResult(PGconn *conn)
22462190
break;
22472191
}
22482192

2249-
/* If the next command we expect is CLOSE, read and consume it */
2250-
if (conn->asyncStatus == PGASYNC_PIPELINE_IDLE &&
2251-
conn->cmd_queue_head &&
2252-
conn->cmd_queue_head->queryclass == PGQUERY_CLOSE)
2253-
{
2254-
if (res && res->resultStatus != PGRES_FATAL_ERROR)
2255-
{
2256-
conn->asyncStatus = PGASYNC_BUSY;
2257-
parseInput(conn);
2258-
conn->asyncStatus = PGASYNC_PIPELINE_IDLE;
2259-
}
2260-
else
2261-
/* we won't ever see the Close */
2262-
pqCommandQueueAdvance(conn);
2263-
}
2264-
22652193
/* Time to fire PGEVT_RESULTCREATE events, if there are any */
22662194
if (res && res->nEvents > 0)
22672195
(void) PQfireResultCreateEvents(conn, res);
@@ -2977,8 +2905,9 @@ PQfn(PGconn *conn,
29772905

29782906
if (conn->pipelineStatus != PQ_PIPELINE_OFF)
29792907
{
2980-
appendPQExpBufferStr(&conn->errorMessage,
2981-
libpq_gettext("PQfn not allowed in pipeline mode\n"));
2908+
appendPQExpBuffer(&conn->errorMessage,
2909+
libpq_gettext("%s not allowed in pipeline mode\n"),
2910+
"PQfn");
29822911
return NULL;
29832912
}
29842913

src/interfaces/libpq/fe-protocol3.c

Lines changed: 1 addition & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -282,24 +282,8 @@ pqParseInput3(PGconn *conn)
282282
}
283283
break;
284284
case '2': /* Bind Complete */
285-
/* Nothing to do for this message type */
286-
break;
287285
case '3': /* Close Complete */
288-
/*
289-
* If we get CloseComplete when waiting for it, consume
290-
* the queue element and keep going. A result is not
291-
* expected from this message; it is just there so that
292-
* we know to wait for it when PQsendQuery is used in
293-
* pipeline mode, before going in IDLE state. Failing to
294-
* do this makes us receive CloseComplete when IDLE, which
295-
* creates problems.
296-
*/
297-
if (conn->cmd_queue_head &&
298-
conn->cmd_queue_head->queryclass == PGQUERY_CLOSE)
299-
{
300-
pqCommandQueueAdvance(conn);
301-
}
302-
286+
/* Nothing to do for these message types */
303287
break;
304288
case 'S': /* parameter status */
305289
if (getParameterStatus(conn))

src/test/modules/libpq_pipeline/libpq_pipeline.c

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,18 @@ test_disallowed_in_pipeline(PGconn *conn)
106106
res = PQexec(conn, "SELECT 1");
107107
if (PQresultStatus(res) != PGRES_FATAL_ERROR)
108108
pg_fatal("PQexec should fail in pipeline mode but succeeded");
109+
if (strcmp(PQerrorMessage(conn),
110+
"synchronous command execution functions are not allowed in pipeline mode\n") != 0)
111+
pg_fatal("did not get expected error message; got: \"%s\"",
112+
PQerrorMessage(conn));
113+
114+
/* PQsendQuery should fail in pipeline mode */
115+
if (PQsendQuery(conn, "SELECT 1") != 0)
116+
pg_fatal("PQsendQuery should fail in pipeline mode but succeeded");
117+
if (strcmp(PQerrorMessage(conn),
118+
"PQsendQuery not allowed in pipeline mode\n") != 0)
119+
pg_fatal("did not get expected error message; got: \"%s\"",
120+
PQerrorMessage(conn));
109121

110122
/* Entering pipeline mode when already in pipeline mode is OK */
111123
if (PQenterPipelineMode(conn) != 1)

0 commit comments

Comments
 (0)