From 5c7671425fc3d423b66ce6308730685c2a7d8a64 Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Mon, 14 Jan 2008 18:46:17 +0000 Subject: [PATCH] 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. --- doc/src/sgml/protocol.sgml | 15 ++- src/interfaces/libpq/fe-protocol3.c | 137 +++++++++++++++++++--------- 2 files changed, 104 insertions(+), 48 deletions(-) diff --git a/doc/src/sgml/protocol.sgml b/doc/src/sgml/protocol.sgml index 978f29c42d..d32f26fb7b 100644 --- a/doc/src/sgml/protocol.sgml +++ b/doc/src/sgml/protocol.sgml @@ -1,4 +1,4 @@ - + Frontend/Backend Protocol @@ -1039,9 +1039,16 @@ 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. + + + + 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 ). Otherwise, any message type other than + CopyData or CopyDone may be treated as terminating copy-out mode. diff --git a/src/interfaces/libpq/fe-protocol3.c b/src/interfaces/libpq/fe-protocol3.c index ec8b0c0b30..2f57500e4f 100644 --- a/src/interfaces/libpq/fe-protocol3.c +++ b/src/interfaces/libpq/fe-protocol3.c @@ -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