Skip to content

Commit 5c76714

Browse files
committed
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.
1 parent 7aa4164 commit 5c76714

File tree

2 files changed

+104
-48
lines changed

2 files changed

+104
-48
lines changed

doc/src/sgml/protocol.sgml

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<!-- $PostgreSQL: pgsql/doc/src/sgml/protocol.sgml,v 1.70 2008/01/09 05:27:22 alvherre Exp $ -->
1+
<!-- $PostgreSQL: pgsql/doc/src/sgml/protocol.sgml,v 1.71 2008/01/14 18:46:17 tgl Exp $ -->
22

33
<chapter id="protocol">
44
<title>Frontend/Backend Protocol</title>
@@ -1039,9 +1039,16 @@
10391039
<para>
10401040
In the event of a backend-detected error during copy-out mode,
10411041
the backend will issue an ErrorResponse message and revert to normal
1042-
processing. The frontend should treat receipt of ErrorResponse (or
1043-
indeed any message type other than CopyData or CopyDone) as terminating
1044-
the copy-out mode.
1042+
processing. The frontend should treat receipt of ErrorResponse as
1043+
terminating the copy-out mode.
1044+
</para>
1045+
1046+
<para>
1047+
It is possible for NoticeResponse messages to be interspersed between
1048+
CopyData messages; frontends must handle this case, and should be
1049+
prepared for other asynchronous message types as well (see <xref
1050+
linkend="protocol-async">). Otherwise, any message type other than
1051+
CopyData or CopyDone may be treated as terminating copy-out mode.
10451052
</para>
10461053

10471054
<para>

src/interfaces/libpq/fe-protocol3.c

Lines changed: 93 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
*
99
*
1010
* IDENTIFICATION
11-
* $PostgreSQL: pgsql/src/interfaces/libpq/fe-protocol3.c,v 1.31 2008/01/01 19:46:00 momjian Exp $
11+
* $PostgreSQL: pgsql/src/interfaces/libpq/fe-protocol3.c,v 1.32 2008/01/14 18:46:17 tgl Exp $
1212
*
1313
*-------------------------------------------------------------------------
1414
*/
@@ -1274,16 +1274,13 @@ getReadyForQuery(PGconn *conn)
12741274
}
12751275

12761276
/*
1277-
* PQgetCopyData - read a row of data from the backend during COPY OUT
1277+
* getCopyDataMessage - fetch next CopyData message, process async messages
12781278
*
1279-
* If successful, sets *buffer to point to a malloc'd row of data, and
1280-
* returns row length (always > 0) as result.
1281-
* Returns 0 if no row available yet (only possible if async is true),
1282-
* -1 if end of copy (consult PQgetResult), or -2 if error (consult
1283-
* PQerrorMessage).
1279+
* Returns length word of CopyData message (> 0), or 0 if no complete
1280+
* message available, -1 if end of copy, -2 if error.
12841281
*/
1285-
int
1286-
pqGetCopyData3(PGconn *conn, char **buffer, int async)
1282+
static int
1283+
getCopyDataMessage(PGconn *conn)
12871284
{
12881285
char id;
12891286
int msgLength;
@@ -1298,22 +1295,94 @@ pqGetCopyData3(PGconn *conn, char **buffer, int async)
12981295
*/
12991296
conn->inCursor = conn->inStart;
13001297
if (pqGetc(&id, conn))
1301-
goto nodata;
1298+
return 0;
13021299
if (pqGetInt(&msgLength, 4, conn))
1303-
goto nodata;
1300+
return 0;
1301+
if (msgLength < 4)
1302+
{
1303+
handleSyncLoss(conn, id, msgLength);
1304+
return -2;
1305+
}
13041306
avail = conn->inEnd - conn->inCursor;
13051307
if (avail < msgLength - 4)
1306-
goto nodata;
1308+
return 0;
13071309

13081310
/*
1309-
* If it's anything except Copy Data, exit COPY_OUT mode and let
1310-
* caller read status with PQgetResult(). The normal case is that
1311-
* it's Copy Done, but we let parseInput read that.
1311+
* If it's a legitimate async message type, process it. (NOTIFY
1312+
* messages are not currently possible here, but we handle them for
1313+
* completeness. NOTICE is definitely possible, and ParameterStatus
1314+
* could probably be made to happen.) Otherwise, if it's anything
1315+
* except Copy Data, report end-of-copy.
13121316
*/
1313-
if (id != 'd')
1317+
switch (id)
13141318
{
1315-
conn->asyncStatus = PGASYNC_BUSY;
1316-
return -1;
1319+
case 'A': /* NOTIFY */
1320+
if (getNotify(conn))
1321+
return 0;
1322+
break;
1323+
case 'N': /* NOTICE */
1324+
if (pqGetErrorNotice3(conn, false))
1325+
return 0;
1326+
break;
1327+
case 'S': /* ParameterStatus */
1328+
if (getParameterStatus(conn))
1329+
return 0;
1330+
break;
1331+
case 'd': /* Copy Data, pass it back to caller */
1332+
return msgLength;
1333+
default: /* treat as end of copy */
1334+
return -1;
1335+
}
1336+
1337+
/* Drop the processed message and loop around for another */
1338+
conn->inStart = conn->inCursor;
1339+
}
1340+
}
1341+
1342+
/*
1343+
* PQgetCopyData - read a row of data from the backend during COPY OUT
1344+
*
1345+
* If successful, sets *buffer to point to a malloc'd row of data, and
1346+
* returns row length (always > 0) as result.
1347+
* Returns 0 if no row available yet (only possible if async is true),
1348+
* -1 if end of copy (consult PQgetResult), or -2 if error (consult
1349+
* PQerrorMessage).
1350+
*/
1351+
int
1352+
pqGetCopyData3(PGconn *conn, char **buffer, int async)
1353+
{
1354+
int msgLength;
1355+
1356+
for (;;)
1357+
{
1358+
/*
1359+
* Collect the next input message. To make life simpler for async
1360+
* callers, we keep returning 0 until the next message is fully
1361+
* available, even if it is not Copy Data.
1362+
*/
1363+
msgLength = getCopyDataMessage(conn);
1364+
if (msgLength < 0)
1365+
{
1366+
/*
1367+
* On end-of-copy, exit COPY_OUT mode and let caller read status
1368+
* with PQgetResult(). The normal case is that it's Copy Done,
1369+
* but we let parseInput read that. If error, we expect the
1370+
* state was already changed.
1371+
*/
1372+
if (msgLength == -1)
1373+
conn->asyncStatus = PGASYNC_BUSY;
1374+
return msgLength; /* end-of-copy or error */
1375+
}
1376+
if (msgLength == 0)
1377+
{
1378+
/* Don't block if async read requested */
1379+
if (async)
1380+
return 0;
1381+
/* Need to load more data */
1382+
if (pqWait(TRUE, FALSE, conn) ||
1383+
pqReadData(conn) < 0)
1384+
return -2;
1385+
continue;
13171386
}
13181387

13191388
/*
@@ -1341,16 +1410,6 @@ pqGetCopyData3(PGconn *conn, char **buffer, int async)
13411410

13421411
/* Empty, so drop it and loop around for another */
13431412
conn->inStart = conn->inCursor;
1344-
continue;
1345-
1346-
nodata:
1347-
/* Don't block if async read requested */
1348-
if (async)
1349-
return 0;
1350-
/* Need to load more data */
1351-
if (pqWait(TRUE, FALSE, conn) ||
1352-
pqReadData(conn) < 0)
1353-
return -2;
13541413
}
13551414
}
13561415

@@ -1413,7 +1472,6 @@ pqGetline3(PGconn *conn, char *s, int maxlen)
14131472
int
14141473
pqGetlineAsync3(PGconn *conn, char *buffer, int bufsize)
14151474
{
1416-
char id;
14171475
int msgLength;
14181476
int avail;
14191477

@@ -1424,22 +1482,13 @@ pqGetlineAsync3(PGconn *conn, char *buffer, int bufsize)
14241482
* Recognize the next input message. To make life simpler for async
14251483
* callers, we keep returning 0 until the next message is fully available
14261484
* even if it is not Copy Data. This should keep PQendcopy from blocking.
1485+
* (Note: unlike pqGetCopyData3, we do not change asyncStatus here.)
14271486
*/
1428-
conn->inCursor = conn->inStart;
1429-
if (pqGetc(&id, conn))
1430-
return 0;
1431-
if (pqGetInt(&msgLength, 4, conn))
1432-
return 0;
1433-
avail = conn->inEnd - conn->inCursor;
1434-
if (avail < msgLength - 4)
1435-
return 0;
1436-
1437-
/*
1438-
* Cannot proceed unless it's a Copy Data message. Anything else means
1439-
* end of copy mode.
1440-
*/
1441-
if (id != 'd')
1442-
return -1;
1487+
msgLength = getCopyDataMessage(conn);
1488+
if (msgLength < 0)
1489+
return -1; /* end-of-copy or error */
1490+
if (msgLength == 0)
1491+
return 0; /* no data yet */
14431492

14441493
/*
14451494
* Move data from libpq's buffer to the caller's. In the case where a

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy