mirror of
https://git.postgresql.org/git/postgresql.git
synced 2024-11-27 07:21:09 +08:00
Fix an ancient oversight in libpq's handling of V3-protocol COPY OUT mode:
we need to be able to swallow NOTICE messages, and potentially also ParameterStatus messages (although the latter would be a bit weird), without exiting COPY OUT state. Fix it, and adjust the protocol documentation to emphasize the need for this. Per off-list report from Alexander Galler.
This commit is contained in:
parent
7aa4164363
commit
5c7671425f
@ -1,4 +1,4 @@
|
||||
<!-- $PostgreSQL: pgsql/doc/src/sgml/protocol.sgml,v 1.70 2008/01/09 05:27:22 alvherre Exp $ -->
|
||||
<!-- $PostgreSQL: pgsql/doc/src/sgml/protocol.sgml,v 1.71 2008/01/14 18:46:17 tgl Exp $ -->
|
||||
|
||||
<chapter id="protocol">
|
||||
<title>Frontend/Backend Protocol</title>
|
||||
@ -1039,9 +1039,16 @@
|
||||
<para>
|
||||
In the event of a backend-detected error during copy-out mode,
|
||||
the backend will issue an ErrorResponse message and revert to normal
|
||||
processing. The frontend should treat receipt of ErrorResponse (or
|
||||
indeed any message type other than CopyData or CopyDone) as terminating
|
||||
the copy-out mode.
|
||||
processing. The frontend should treat receipt of ErrorResponse as
|
||||
terminating the copy-out mode.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
It is possible for NoticeResponse messages to be interspersed between
|
||||
CopyData messages; frontends must handle this case, and should be
|
||||
prepared for other asynchronous message types as well (see <xref
|
||||
linkend="protocol-async">). Otherwise, any message type other than
|
||||
CopyData or CopyDone may be treated as terminating copy-out mode.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
|
@ -8,7 +8,7 @@
|
||||
*
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $PostgreSQL: pgsql/src/interfaces/libpq/fe-protocol3.c,v 1.31 2008/01/01 19:46:00 momjian Exp $
|
||||
* $PostgreSQL: pgsql/src/interfaces/libpq/fe-protocol3.c,v 1.32 2008/01/14 18:46:17 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@ -1274,16 +1274,13 @@ getReadyForQuery(PGconn *conn)
|
||||
}
|
||||
|
||||
/*
|
||||
* PQgetCopyData - read a row of data from the backend during COPY OUT
|
||||
* getCopyDataMessage - fetch next CopyData message, process async messages
|
||||
*
|
||||
* If successful, sets *buffer to point to a malloc'd row of data, and
|
||||
* returns row length (always > 0) as result.
|
||||
* Returns 0 if no row available yet (only possible if async is true),
|
||||
* -1 if end of copy (consult PQgetResult), or -2 if error (consult
|
||||
* PQerrorMessage).
|
||||
* Returns length word of CopyData message (> 0), or 0 if no complete
|
||||
* message available, -1 if end of copy, -2 if error.
|
||||
*/
|
||||
int
|
||||
pqGetCopyData3(PGconn *conn, char **buffer, int async)
|
||||
static int
|
||||
getCopyDataMessage(PGconn *conn)
|
||||
{
|
||||
char id;
|
||||
int msgLength;
|
||||
@ -1298,22 +1295,94 @@ pqGetCopyData3(PGconn *conn, char **buffer, int async)
|
||||
*/
|
||||
conn->inCursor = conn->inStart;
|
||||
if (pqGetc(&id, conn))
|
||||
goto nodata;
|
||||
return 0;
|
||||
if (pqGetInt(&msgLength, 4, conn))
|
||||
goto nodata;
|
||||
return 0;
|
||||
if (msgLength < 4)
|
||||
{
|
||||
handleSyncLoss(conn, id, msgLength);
|
||||
return -2;
|
||||
}
|
||||
avail = conn->inEnd - conn->inCursor;
|
||||
if (avail < msgLength - 4)
|
||||
goto nodata;
|
||||
return 0;
|
||||
|
||||
/*
|
||||
* If it's anything except Copy Data, exit COPY_OUT mode and let
|
||||
* caller read status with PQgetResult(). The normal case is that
|
||||
* it's Copy Done, but we let parseInput read that.
|
||||
* If it's a legitimate async message type, process it. (NOTIFY
|
||||
* messages are not currently possible here, but we handle them for
|
||||
* completeness. NOTICE is definitely possible, and ParameterStatus
|
||||
* could probably be made to happen.) Otherwise, if it's anything
|
||||
* except Copy Data, report end-of-copy.
|
||||
*/
|
||||
if (id != 'd')
|
||||
switch (id)
|
||||
{
|
||||
conn->asyncStatus = PGASYNC_BUSY;
|
||||
return -1;
|
||||
case 'A': /* NOTIFY */
|
||||
if (getNotify(conn))
|
||||
return 0;
|
||||
break;
|
||||
case 'N': /* NOTICE */
|
||||
if (pqGetErrorNotice3(conn, false))
|
||||
return 0;
|
||||
break;
|
||||
case 'S': /* ParameterStatus */
|
||||
if (getParameterStatus(conn))
|
||||
return 0;
|
||||
break;
|
||||
case 'd': /* Copy Data, pass it back to caller */
|
||||
return msgLength;
|
||||
default: /* treat as end of copy */
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Drop the processed message and loop around for another */
|
||||
conn->inStart = conn->inCursor;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* PQgetCopyData - read a row of data from the backend during COPY OUT
|
||||
*
|
||||
* If successful, sets *buffer to point to a malloc'd row of data, and
|
||||
* returns row length (always > 0) as result.
|
||||
* Returns 0 if no row available yet (only possible if async is true),
|
||||
* -1 if end of copy (consult PQgetResult), or -2 if error (consult
|
||||
* PQerrorMessage).
|
||||
*/
|
||||
int
|
||||
pqGetCopyData3(PGconn *conn, char **buffer, int async)
|
||||
{
|
||||
int msgLength;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
/*
|
||||
* Collect the next input message. To make life simpler for async
|
||||
* callers, we keep returning 0 until the next message is fully
|
||||
* available, even if it is not Copy Data.
|
||||
*/
|
||||
msgLength = getCopyDataMessage(conn);
|
||||
if (msgLength < 0)
|
||||
{
|
||||
/*
|
||||
* On end-of-copy, exit COPY_OUT mode and let caller read status
|
||||
* with PQgetResult(). The normal case is that it's Copy Done,
|
||||
* but we let parseInput read that. If error, we expect the
|
||||
* state was already changed.
|
||||
*/
|
||||
if (msgLength == -1)
|
||||
conn->asyncStatus = PGASYNC_BUSY;
|
||||
return msgLength; /* end-of-copy or error */
|
||||
}
|
||||
if (msgLength == 0)
|
||||
{
|
||||
/* Don't block if async read requested */
|
||||
if (async)
|
||||
return 0;
|
||||
/* Need to load more data */
|
||||
if (pqWait(TRUE, FALSE, conn) ||
|
||||
pqReadData(conn) < 0)
|
||||
return -2;
|
||||
continue;
|
||||
}
|
||||
|
||||
/*
|
||||
@ -1341,16 +1410,6 @@ pqGetCopyData3(PGconn *conn, char **buffer, int async)
|
||||
|
||||
/* Empty, so drop it and loop around for another */
|
||||
conn->inStart = conn->inCursor;
|
||||
continue;
|
||||
|
||||
nodata:
|
||||
/* Don't block if async read requested */
|
||||
if (async)
|
||||
return 0;
|
||||
/* Need to load more data */
|
||||
if (pqWait(TRUE, FALSE, conn) ||
|
||||
pqReadData(conn) < 0)
|
||||
return -2;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1413,7 +1472,6 @@ pqGetline3(PGconn *conn, char *s, int maxlen)
|
||||
int
|
||||
pqGetlineAsync3(PGconn *conn, char *buffer, int bufsize)
|
||||
{
|
||||
char id;
|
||||
int msgLength;
|
||||
int avail;
|
||||
|
||||
@ -1424,22 +1482,13 @@ pqGetlineAsync3(PGconn *conn, char *buffer, int bufsize)
|
||||
* Recognize the next input message. To make life simpler for async
|
||||
* callers, we keep returning 0 until the next message is fully available
|
||||
* even if it is not Copy Data. This should keep PQendcopy from blocking.
|
||||
* (Note: unlike pqGetCopyData3, we do not change asyncStatus here.)
|
||||
*/
|
||||
conn->inCursor = conn->inStart;
|
||||
if (pqGetc(&id, conn))
|
||||
return 0;
|
||||
if (pqGetInt(&msgLength, 4, conn))
|
||||
return 0;
|
||||
avail = conn->inEnd - conn->inCursor;
|
||||
if (avail < msgLength - 4)
|
||||
return 0;
|
||||
|
||||
/*
|
||||
* Cannot proceed unless it's a Copy Data message. Anything else means
|
||||
* end of copy mode.
|
||||
*/
|
||||
if (id != 'd')
|
||||
return -1;
|
||||
msgLength = getCopyDataMessage(conn);
|
||||
if (msgLength < 0)
|
||||
return -1; /* end-of-copy or error */
|
||||
if (msgLength == 0)
|
||||
return 0; /* no data yet */
|
||||
|
||||
/*
|
||||
* Move data from libpq's buffer to the caller's. In the case where a
|
||||
|
Loading…
Reference in New Issue
Block a user