Skip to content

Commit bd8ac90

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 27e0441 commit bd8ac90

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
@@ -4599,8 +4599,7 @@ int PQsendQuery(PGconn *conn, const char *command);
45994599
</para>
46004600

46014601
<para>
4602-
In pipeline mode, command strings containing more than one SQL command
4603-
are disallowed.
4602+
In pipeline mode, this function is disallowed.
46044603
</para>
46054604
</listitem>
46064605
</varlistentry>
@@ -5054,6 +5053,7 @@ int PQflush(PGconn *conn);
50545053
<xref linkend="libpq-PQpipelineStatus"/> can be used
50555054
to test whether pipeline mode is active.
50565055
In pipeline mode, only <link linkend="libpq-async">asynchronous operations</link>
5056+
that utilize the extended query protocol
50575057
are permitted, command strings containing multiple SQL commands are
50585058
disallowed, and so is <literal>COPY</literal>.
50595059
Using synchronous command execution functions
@@ -5065,6 +5065,8 @@ int PQflush(PGconn *conn);
50655065
<function>PQdescribePrepared</function>,
50665066
<function>PQdescribePortal</function>,
50675067
is an error condition.
5068+
<function>PQsendQuery</function> is
5069+
also disallowed, because it uses the simple query protocol.
50685070
Once all dispatched commands have had their results processed, and
50695071
the end pipeline result has been consumed, the application may return
50705072
to non-pipelined mode with <xref linkend="libpq-PQexitPipelineMode"/>.
@@ -5093,8 +5095,7 @@ int PQflush(PGconn *conn);
50935095

50945096
<para>
50955097
After entering pipeline mode, the application dispatches requests using
5096-
<xref linkend="libpq-PQsendQuery"/>,
5097-
<xref linkend="libpq-PQsendQueryParams"/>,
5098+
<xref linkend="libpq-PQsendQueryParams"/>
50985099
or its prepared-query sibling
50995100
<xref linkend="libpq-PQsendQueryPrepared"/>.
51005101
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
@@ -1437,7 +1437,6 @@ static int
14371437
PQsendQueryInternal(PGconn *conn, const char *query, bool newQuery)
14381438
{
14391439
PGcmdQueueEntry *entry = NULL;
1440-
PGcmdQueueEntry *entry2 = NULL;
14411440

14421441
if (!PQsendQueryStart(conn, newQuery))
14431442
return 0;
@@ -1450,103 +1449,48 @@ PQsendQueryInternal(PGconn *conn, const char *query, bool newQuery)
14501449
return 0;
14511450
}
14521451

1453-
entry = pqAllocCmdQueueEntry(conn);
1454-
if (entry == NULL)
1455-
return 0; /* error msg already set */
14561452
if (conn->pipelineStatus != PQ_PIPELINE_OFF)
14571453
{
1458-
entry2 = pqAllocCmdQueueEntry(conn);
1459-
if (entry2 == NULL)
1460-
goto sendFailed;
1454+
appendPQExpBuffer(&conn->errorMessage,
1455+
libpq_gettext("%s not allowed in pipeline mode\n"),
1456+
"PQsendQuery");
1457+
return 0;
14611458
}
14621459

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

1518-
entry->queryclass = PGQUERY_EXTENDED;
1519-
entry->query = strdup(query);
1520-
}
1475+
/* remember we are using simple query protocol */
1476+
entry->queryclass = PGQUERY_SIMPLE;
1477+
/* and remember the query text too, if possible */
1478+
entry->query = strdup(query);
15211479

15221480
/*
15231481
* Give the data a push. In nonblock mode, don't complain if we're unable
15241482
* to send it all; PQgetResult() will do any additional flushing needed.
15251483
*/
1526-
if (pqPipelineFlush(conn) < 0)
1484+
if (pqFlush(conn) < 0)
15271485
goto sendFailed;
15281486

15291487
/* OK, it's launched! */
15301488
pqAppendCmdQueueEntry(conn, entry);
15311489

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

15471492
sendFailed:
15481493
pqRecycleCmdQueueEntry(conn, entry);
1549-
pqRecycleCmdQueueEntry(conn, entry2);
15501494
/* error message should be set up already */
15511495
return 0;
15521496
}
@@ -2250,22 +2194,6 @@ PQgetResult(PGconn *conn)
22502194
break;
22512195
}
22522196

2253-
/* If the next command we expect is CLOSE, read and consume it */
2254-
if (conn->asyncStatus == PGASYNC_PIPELINE_IDLE &&
2255-
conn->cmd_queue_head &&
2256-
conn->cmd_queue_head->queryclass == PGQUERY_CLOSE)
2257-
{
2258-
if (res && res->resultStatus != PGRES_FATAL_ERROR)
2259-
{
2260-
conn->asyncStatus = PGASYNC_BUSY;
2261-
parseInput(conn);
2262-
conn->asyncStatus = PGASYNC_PIPELINE_IDLE;
2263-
}
2264-
else
2265-
/* we won't ever see the Close */
2266-
pqCommandQueueAdvance(conn);
2267-
}
2268-
22692197
/* Time to fire PGEVT_RESULTCREATE events, if there are any */
22702198
if (res && res->nEvents > 0)
22712199
(void) PQfireResultCreateEvents(conn, res);
@@ -2982,8 +2910,9 @@ PQfn(PGconn *conn,
29822910

29832911
if (conn->pipelineStatus != PQ_PIPELINE_OFF)
29842912
{
2985-
appendPQExpBufferStr(&conn->errorMessage,
2986-
libpq_gettext("PQfn not allowed in pipeline mode\n"));
2913+
appendPQExpBuffer(&conn->errorMessage,
2914+
libpq_gettext("%s not allowed in pipeline mode\n"),
2915+
"PQfn");
29872916
return NULL;
29882917
}
29892918

src/interfaces/libpq/fe-protocol3.c

Lines changed: 1 addition & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -284,24 +284,8 @@ pqParseInput3(PGconn *conn)
284284
}
285285
break;
286286
case '2': /* Bind Complete */
287-
/* Nothing to do for this message type */
288-
break;
289287
case '3': /* Close Complete */
290-
/*
291-
* If we get CloseComplete when waiting for it, consume
292-
* the queue element and keep going. A result is not
293-
* expected from this message; it is just there so that
294-
* we know to wait for it when PQsendQuery is used in
295-
* pipeline mode, before going in IDLE state. Failing to
296-
* do this makes us receive CloseComplete when IDLE, which
297-
* creates problems.
298-
*/
299-
if (conn->cmd_queue_head &&
300-
conn->cmd_queue_head->queryclass == PGQUERY_CLOSE)
301-
{
302-
pqCommandQueueAdvance(conn);
303-
}
304-
288+
/* Nothing to do for these message types */
305289
break;
306290
case 'S': /* parameter status */
307291
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
@@ -108,6 +108,18 @@ test_disallowed_in_pipeline(PGconn *conn)
108108
res = PQexec(conn, "SELECT 1");
109109
if (PQresultStatus(res) != PGRES_FATAL_ERROR)
110110
pg_fatal("PQexec should fail in pipeline mode but succeeded");
111+
if (strcmp(PQerrorMessage(conn),
112+
"synchronous command execution functions are not allowed in pipeline mode\n") != 0)
113+
pg_fatal("did not get expected error message; got: \"%s\"",
114+
PQerrorMessage(conn));
115+
116+
/* PQsendQuery should fail in pipeline mode */
117+
if (PQsendQuery(conn, "SELECT 1") != 0)
118+
pg_fatal("PQsendQuery should fail in pipeline mode but succeeded");
119+
if (strcmp(PQerrorMessage(conn),
120+
"PQsendQuery not allowed in pipeline mode\n") != 0)
121+
pg_fatal("did not get expected error message; got: \"%s\"",
122+
PQerrorMessage(conn));
111123

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

0 commit comments

Comments
 (0)