diff options
author | Tom Lane | 2008-01-14 18:46:49 +0000 |
---|---|---|
committer | Tom Lane | 2008-01-14 18:46:49 +0000 |
commit | 865cddd9105c03b52f6c1af666c5f6ccd20fc7d4 (patch) | |
tree | dafa90d3d80d747a79b9327b98a707849c41394f | |
parent | cdc58fda79b1152ee2db9a1c94a2b7c9f52dd3e6 (diff) |
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.
-rw-r--r-- | doc/src/sgml/protocol.sgml | 13 | ||||
-rw-r--r-- | src/interfaces/libpq/fe-protocol3.c | 135 |
2 files changed, 102 insertions, 46 deletions
diff --git a/doc/src/sgml/protocol.sgml b/doc/src/sgml/protocol.sgml index 2878e175ff..13ab98a4fe 100644 --- a/doc/src/sgml/protocol.sgml +++ b/doc/src/sgml/protocol.sgml @@ -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> diff --git a/src/interfaces/libpq/fe-protocol3.c b/src/interfaces/libpq/fe-protocol3.c index f4a8e2a2d3..5e352480cd 100644 --- a/src/interfaces/libpq/fe-protocol3.c +++ b/src/interfaces/libpq/fe-protocol3.c @@ -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 |