mirror of
https://git.postgresql.org/git/postgresql.git
synced 2025-01-30 19:00:29 +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
691189c9d6
commit
dcd462a9c0
@ -1,4 +1,4 @@
|
|||||||
<!-- $PostgreSQL: pgsql/doc/src/sgml/protocol.sgml,v 1.66 2006/09/06 20:40:47 tgl Exp $ -->
|
<!-- $PostgreSQL: pgsql/doc/src/sgml/protocol.sgml,v 1.66.2.1 2008/01/14 18:46:25 tgl Exp $ -->
|
||||||
|
|
||||||
<chapter id="protocol">
|
<chapter id="protocol">
|
||||||
<title>Frontend/Backend Protocol</title>
|
<title>Frontend/Backend Protocol</title>
|
||||||
@ -993,9 +993,16 @@
|
|||||||
<para>
|
<para>
|
||||||
In the event of a backend-detected error during copy-out mode,
|
In the event of a backend-detected error during copy-out mode,
|
||||||
the backend will issue an ErrorResponse message and revert to normal
|
the backend will issue an ErrorResponse message and revert to normal
|
||||||
processing. The frontend should treat receipt of ErrorResponse (or
|
processing. The frontend should treat receipt of ErrorResponse as
|
||||||
indeed any message type other than CopyData or CopyDone) as terminating
|
terminating the copy-out mode.
|
||||||
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>
|
||||||
|
|
||||||
<para>
|
<para>
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
*
|
*
|
||||||
*
|
*
|
||||||
* IDENTIFICATION
|
* IDENTIFICATION
|
||||||
* $PostgreSQL: pgsql/src/interfaces/libpq/fe-protocol3.c,v 1.29 2006/10/04 00:30:13 momjian Exp $
|
* $PostgreSQL: pgsql/src/interfaces/libpq/fe-protocol3.c,v 1.29.2.1 2008/01/14 18:46:25 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 length word of CopyData message (> 0), or 0 if no complete
|
||||||
* returns row length (always > 0) as result.
|
* message available, -1 if end of copy, -2 if error.
|
||||||
* 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
|
static int
|
||||||
pqGetCopyData3(PGconn *conn, char **buffer, int async)
|
getCopyDataMessage(PGconn *conn)
|
||||||
{
|
{
|
||||||
char id;
|
char id;
|
||||||
int msgLength;
|
int msgLength;
|
||||||
@ -1298,24 +1295,96 @@ pqGetCopyData3(PGconn *conn, char **buffer, int async)
|
|||||||
*/
|
*/
|
||||||
conn->inCursor = conn->inStart;
|
conn->inCursor = conn->inStart;
|
||||||
if (pqGetc(&id, conn))
|
if (pqGetc(&id, conn))
|
||||||
goto nodata;
|
return 0;
|
||||||
if (pqGetInt(&msgLength, 4, conn))
|
if (pqGetInt(&msgLength, 4, conn))
|
||||||
goto nodata;
|
return 0;
|
||||||
|
if (msgLength < 4)
|
||||||
|
{
|
||||||
|
handleSyncLoss(conn, id, msgLength);
|
||||||
|
return -2;
|
||||||
|
}
|
||||||
avail = conn->inEnd - conn->inCursor;
|
avail = conn->inEnd - conn->inCursor;
|
||||||
if (avail < msgLength - 4)
|
if (avail < msgLength - 4)
|
||||||
goto nodata;
|
return 0;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* If it's anything except Copy Data, exit COPY_OUT mode and let
|
* If it's a legitimate async message type, process it. (NOTIFY
|
||||||
* caller read status with PQgetResult(). The normal case is that
|
* messages are not currently possible here, but we handle them for
|
||||||
* it's Copy Done, but we let parseInput read that.
|
* 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;
|
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;
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Drop zero-length messages (shouldn't happen anyway). Otherwise
|
* Drop zero-length messages (shouldn't happen anyway). Otherwise
|
||||||
* pass the data back to the caller.
|
* pass the data back to the caller.
|
||||||
@ -1341,16 +1410,6 @@ pqGetCopyData3(PGconn *conn, char **buffer, int async)
|
|||||||
|
|
||||||
/* Empty, so drop it and loop around for another */
|
/* Empty, so drop it and loop around for another */
|
||||||
conn->inStart = conn->inCursor;
|
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
|
int
|
||||||
pqGetlineAsync3(PGconn *conn, char *buffer, int bufsize)
|
pqGetlineAsync3(PGconn *conn, char *buffer, int bufsize)
|
||||||
{
|
{
|
||||||
char id;
|
|
||||||
int msgLength;
|
int msgLength;
|
||||||
int avail;
|
int avail;
|
||||||
|
|
||||||
@ -1424,22 +1482,13 @@ pqGetlineAsync3(PGconn *conn, char *buffer, int bufsize)
|
|||||||
* Recognize the next input message. To make life simpler for async
|
* Recognize the next input message. To make life simpler for async
|
||||||
* callers, we keep returning 0 until the next message is fully available
|
* 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.
|
* 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;
|
msgLength = getCopyDataMessage(conn);
|
||||||
if (pqGetc(&id, conn))
|
if (msgLength < 0)
|
||||||
return 0;
|
return -1; /* end-of-copy or error */
|
||||||
if (pqGetInt(&msgLength, 4, conn))
|
if (msgLength == 0)
|
||||||
return 0;
|
return 0; /* no data yet */
|
||||||
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;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Move data from libpq's buffer to the caller's. In the case where a
|
* Move data from libpq's buffer to the caller's. In the case where a
|
||||||
|
Loading…
Reference in New Issue
Block a user