mirror of
https://github.com/postgres/postgres.git
synced 2025-04-27 22:56:53 +03:00
Remove PQsendQuery support in pipeline mode
The extended query protocol implementation I added in commit acb7e4eb6b1c 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
This commit is contained in:
parent
27e04412c9
commit
bd8ac900df
@ -4599,8 +4599,7 @@ int PQsendQuery(PGconn *conn, const char *command);
|
|||||||
</para>
|
</para>
|
||||||
|
|
||||||
<para>
|
<para>
|
||||||
In pipeline mode, command strings containing more than one SQL command
|
In pipeline mode, this function is disallowed.
|
||||||
are disallowed.
|
|
||||||
</para>
|
</para>
|
||||||
</listitem>
|
</listitem>
|
||||||
</varlistentry>
|
</varlistentry>
|
||||||
@ -5054,6 +5053,7 @@ int PQflush(PGconn *conn);
|
|||||||
<xref linkend="libpq-PQpipelineStatus"/> can be used
|
<xref linkend="libpq-PQpipelineStatus"/> can be used
|
||||||
to test whether pipeline mode is active.
|
to test whether pipeline mode is active.
|
||||||
In pipeline mode, only <link linkend="libpq-async">asynchronous operations</link>
|
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
|
are permitted, command strings containing multiple SQL commands are
|
||||||
disallowed, and so is <literal>COPY</literal>.
|
disallowed, and so is <literal>COPY</literal>.
|
||||||
Using synchronous command execution functions
|
Using synchronous command execution functions
|
||||||
@ -5065,6 +5065,8 @@ int PQflush(PGconn *conn);
|
|||||||
<function>PQdescribePrepared</function>,
|
<function>PQdescribePrepared</function>,
|
||||||
<function>PQdescribePortal</function>,
|
<function>PQdescribePortal</function>,
|
||||||
is an error condition.
|
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
|
Once all dispatched commands have had their results processed, and
|
||||||
the end pipeline result has been consumed, the application may return
|
the end pipeline result has been consumed, the application may return
|
||||||
to non-pipelined mode with <xref linkend="libpq-PQexitPipelineMode"/>.
|
to non-pipelined mode with <xref linkend="libpq-PQexitPipelineMode"/>.
|
||||||
@ -5093,8 +5095,7 @@ int PQflush(PGconn *conn);
|
|||||||
|
|
||||||
<para>
|
<para>
|
||||||
After entering pipeline mode, the application dispatches requests using
|
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
|
or its prepared-query sibling
|
||||||
<xref linkend="libpq-PQsendQueryPrepared"/>.
|
<xref linkend="libpq-PQsendQueryPrepared"/>.
|
||||||
These requests are queued on the client-side until flushed to the server;
|
These requests are queued on the client-side until flushed to the server;
|
||||||
|
@ -1437,7 +1437,6 @@ static int
|
|||||||
PQsendQueryInternal(PGconn *conn, const char *query, bool newQuery)
|
PQsendQueryInternal(PGconn *conn, const char *query, bool newQuery)
|
||||||
{
|
{
|
||||||
PGcmdQueueEntry *entry = NULL;
|
PGcmdQueueEntry *entry = NULL;
|
||||||
PGcmdQueueEntry *entry2 = NULL;
|
|
||||||
|
|
||||||
if (!PQsendQueryStart(conn, newQuery))
|
if (!PQsendQueryStart(conn, newQuery))
|
||||||
return 0;
|
return 0;
|
||||||
@ -1450,19 +1449,19 @@ PQsendQueryInternal(PGconn *conn, const char *query, bool newQuery)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (conn->pipelineStatus != PQ_PIPELINE_OFF)
|
||||||
|
{
|
||||||
|
appendPQExpBuffer(&conn->errorMessage,
|
||||||
|
libpq_gettext("%s not allowed in pipeline mode\n"),
|
||||||
|
"PQsendQuery");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
entry = pqAllocCmdQueueEntry(conn);
|
entry = pqAllocCmdQueueEntry(conn);
|
||||||
if (entry == NULL)
|
if (entry == NULL)
|
||||||
return 0; /* error msg already set */
|
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) */
|
/* Send the query message(s) */
|
||||||
if (conn->pipelineStatus == PQ_PIPELINE_OFF)
|
|
||||||
{
|
|
||||||
/* construct the outgoing Query message */
|
/* construct the outgoing Query message */
|
||||||
if (pqPutMsgStart('Q', conn) < 0 ||
|
if (pqPutMsgStart('Q', conn) < 0 ||
|
||||||
pqPuts(query, conn) < 0 ||
|
pqPuts(query, conn) < 0 ||
|
||||||
@ -1477,76 +1476,21 @@ PQsendQueryInternal(PGconn *conn, const char *query, bool newQuery)
|
|||||||
entry->queryclass = PGQUERY_SIMPLE;
|
entry->queryclass = PGQUERY_SIMPLE;
|
||||||
/* and remember the query text too, if possible */
|
/* and remember the query text too, if possible */
|
||||||
entry->query = strdup(query);
|
entry->query = strdup(query);
|
||||||
}
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Give the data a push. In nonblock mode, don't complain if we're unable
|
* 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.
|
* to send it all; PQgetResult() will do any additional flushing needed.
|
||||||
*/
|
*/
|
||||||
if (pqPipelineFlush(conn) < 0)
|
if (pqFlush(conn) < 0)
|
||||||
goto sendFailed;
|
goto sendFailed;
|
||||||
|
|
||||||
/* OK, it's launched! */
|
/* OK, it's launched! */
|
||||||
pqAppendCmdQueueEntry(conn, entry);
|
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;
|
return 1;
|
||||||
|
|
||||||
sendFailed:
|
sendFailed:
|
||||||
pqRecycleCmdQueueEntry(conn, entry);
|
pqRecycleCmdQueueEntry(conn, entry);
|
||||||
pqRecycleCmdQueueEntry(conn, entry2);
|
|
||||||
/* error message should be set up already */
|
/* error message should be set up already */
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@ -2250,22 +2194,6 @@ PQgetResult(PGconn *conn)
|
|||||||
break;
|
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 */
|
/* Time to fire PGEVT_RESULTCREATE events, if there are any */
|
||||||
if (res && res->nEvents > 0)
|
if (res && res->nEvents > 0)
|
||||||
(void) PQfireResultCreateEvents(conn, res);
|
(void) PQfireResultCreateEvents(conn, res);
|
||||||
@ -2982,8 +2910,9 @@ PQfn(PGconn *conn,
|
|||||||
|
|
||||||
if (conn->pipelineStatus != PQ_PIPELINE_OFF)
|
if (conn->pipelineStatus != PQ_PIPELINE_OFF)
|
||||||
{
|
{
|
||||||
appendPQExpBufferStr(&conn->errorMessage,
|
appendPQExpBuffer(&conn->errorMessage,
|
||||||
libpq_gettext("PQfn not allowed in pipeline mode\n"));
|
libpq_gettext("%s not allowed in pipeline mode\n"),
|
||||||
|
"PQfn");
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -284,24 +284,8 @@ pqParseInput3(PGconn *conn)
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case '2': /* Bind Complete */
|
case '2': /* Bind Complete */
|
||||||
/* Nothing to do for this message type */
|
|
||||||
break;
|
|
||||||
case '3': /* Close Complete */
|
case '3': /* Close Complete */
|
||||||
/*
|
/* Nothing to do for these message types */
|
||||||
* 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case 'S': /* parameter status */
|
case 'S': /* parameter status */
|
||||||
if (getParameterStatus(conn))
|
if (getParameterStatus(conn))
|
||||||
|
@ -108,6 +108,18 @@ test_disallowed_in_pipeline(PGconn *conn)
|
|||||||
res = PQexec(conn, "SELECT 1");
|
res = PQexec(conn, "SELECT 1");
|
||||||
if (PQresultStatus(res) != PGRES_FATAL_ERROR)
|
if (PQresultStatus(res) != PGRES_FATAL_ERROR)
|
||||||
pg_fatal("PQexec should fail in pipeline mode but succeeded");
|
pg_fatal("PQexec should fail in pipeline mode but succeeded");
|
||||||
|
if (strcmp(PQerrorMessage(conn),
|
||||||
|
"synchronous command execution functions are not allowed in pipeline mode\n") != 0)
|
||||||
|
pg_fatal("did not get expected error message; got: \"%s\"",
|
||||||
|
PQerrorMessage(conn));
|
||||||
|
|
||||||
|
/* PQsendQuery should fail in pipeline mode */
|
||||||
|
if (PQsendQuery(conn, "SELECT 1") != 0)
|
||||||
|
pg_fatal("PQsendQuery should fail in pipeline mode but succeeded");
|
||||||
|
if (strcmp(PQerrorMessage(conn),
|
||||||
|
"PQsendQuery not allowed in pipeline mode\n") != 0)
|
||||||
|
pg_fatal("did not get expected error message; got: \"%s\"",
|
||||||
|
PQerrorMessage(conn));
|
||||||
|
|
||||||
/* Entering pipeline mode when already in pipeline mode is OK */
|
/* Entering pipeline mode when already in pipeline mode is OK */
|
||||||
if (PQenterPipelineMode(conn) != 1)
|
if (PQenterPipelineMode(conn) != 1)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user