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:
Tom Lane 2008-01-14 18:46:17 +00:00
parent 7aa4164363
commit 5c7671425f
2 changed files with 104 additions and 48 deletions

View File

@ -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"> <chapter id="protocol">
<title>Frontend/Backend Protocol</title> <title>Frontend/Backend Protocol</title>
@ -1039,9 +1039,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>

View File

@ -8,7 +8,7 @@
* *
* *
* IDENTIFICATION * 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 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,22 +1295,94 @@ 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 */
return -1; 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 */ /* 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