summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTom Lane2008-01-14 18:46:49 +0000
committerTom Lane2008-01-14 18:46:49 +0000
commit865cddd9105c03b52f6c1af666c5f6ccd20fc7d4 (patch)
treedafa90d3d80d747a79b9327b98a707849c41394f
parentcdc58fda79b1152ee2db9a1c94a2b7c9f52dd3e6 (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.sgml13
-rw-r--r--src/interfaces/libpq/fe-protocol3.c135
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