Skip to content

Commit b8f00a4

Browse files
committed
Clean up error cases in psql's COPY TO STDOUT/FROM STDIN code.
Adjust handleCopyOut() to stop trying to write data once it's failed one time. For typical cases such as out-of-disk-space or broken-pipe, additional attempts aren't going to do anything but waste time, and in any case clean truncation of the output seems like a better behavior than randomly dropping blocks in the middle. Also remove dubious (and misleadingly documented) attempt to force our way out of COPY_OUT state if libpq didn't do that. If we did have a situation like that, it'd be a bug in libpq and would be better fixed there, IMO. We can hope that commit fa4440f took care of any such problems, anyway. Also fix longstanding bug in handleCopyIn(): PQputCopyEnd() only supports a non-null errormsg parameter in protocol version 3, and will actively fail if one is passed in version 2. This would've made our attempts to get out of COPY_IN state after a failure into infinite loops when talking to pre-7.4 servers. Back-patch the COPY_OUT state change business back to 9.2 where it was introduced, and the other two fixes into all supported branches.
1 parent 801c2dc commit b8f00a4

File tree

1 file changed

+37
-38
lines changed

1 file changed

+37
-38
lines changed

src/bin/psql/copy.c

Lines changed: 37 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -445,15 +445,15 @@ handleCopyOut(PGconn *conn, FILE *copystream)
445445
ret = PQgetCopyData(conn, &buf, 0);
446446

447447
if (ret < 0)
448-
break; /* done or error */
448+
break; /* done or server/connection error */
449449

450450
if (buf)
451451
{
452-
if (fwrite(buf, 1, ret, copystream) != ret)
452+
if (OK && fwrite(buf, 1, ret, copystream) != ret)
453453
{
454-
if (OK) /* complain only once, keep reading data */
455-
psql_error("could not write COPY data: %s\n",
456-
strerror(errno));
454+
psql_error("could not write COPY data: %s\n",
455+
strerror(errno));
456+
/* complain only once, keep reading data from server */
457457
OK = false;
458458
}
459459
PQfreemem(buf);
@@ -474,29 +474,18 @@ handleCopyOut(PGconn *conn, FILE *copystream)
474474
}
475475

476476
/*
477-
* Check command status and return to normal libpq state. After a
478-
* client-side error, the server will remain ready to deliver data. The
479-
* cleanest thing is to fully drain and discard that data. If the
480-
* client-side error happened early in a large file, this takes a long
481-
* time. Instead, take advantage of the fact that PQexec() will silently
482-
* end any ongoing PGRES_COPY_OUT state. This does cause us to lose the
483-
* results of any commands following the COPY in a single command string.
484-
* It also only works for protocol version 3. XXX should we clean up
485-
* using the slow way when the connection is using protocol version 2?
477+
* Check command status and return to normal libpq state.
486478
*
487-
* We must not ever return with the status still PGRES_COPY_OUT. Our
488-
* caller is unable to distinguish that situation from reaching the next
489-
* COPY in a command string that happened to contain two consecutive COPY
490-
* TO STDOUT commands. We trust that no condition can make PQexec() fail
491-
* indefinitely while retaining status PGRES_COPY_OUT.
479+
* If for some reason libpq is still reporting PGRES_COPY_OUT state, we
480+
* would like to forcibly exit that state, since our caller would be
481+
* unable to distinguish that situation from reaching the next COPY in a
482+
* command string that happened to contain two consecutive COPY TO STDOUT
483+
* commands. However, libpq provides no API for doing that, and in
484+
* principle it's a libpq bug anyway if PQgetCopyData() returns -1 or -2
485+
* but hasn't exited COPY_OUT state internally. So we ignore the
486+
* possibility here.
492487
*/
493-
while (res = PQgetResult(conn), PQresultStatus(res) == PGRES_COPY_OUT)
494-
{
495-
OK = false;
496-
PQclear(res);
497-
498-
PQexec(conn, "-- clear PGRES_COPY_OUT state");
499-
}
488+
res = PQgetResult(conn);
500489
if (PQresultStatus(res) != PGRES_COMMAND_OK)
501490
{
502491
psql_error("%s", PQerrorMessage(conn));
@@ -539,7 +528,9 @@ handleCopyIn(PGconn *conn, FILE *copystream, bool isbinary)
539528
/* got here with longjmp */
540529

541530
/* Terminate data transfer */
542-
PQputCopyEnd(conn, _("canceled by user"));
531+
PQputCopyEnd(conn,
532+
(PQprotocolVersion(conn) < 3) ? NULL :
533+
_("canceled by user"));
543534

544535
OK = false;
545536
goto copyin_cleanup;
@@ -665,29 +656,37 @@ handleCopyIn(PGconn *conn, FILE *copystream, bool isbinary)
665656
if (ferror(copystream))
666657
OK = false;
667658

668-
/* Terminate data transfer */
659+
/*
660+
* Terminate data transfer. We can't send an error message if we're using
661+
* protocol version 2.
662+
*/
669663
if (PQputCopyEnd(conn,
670-
OK ? NULL : _("aborted because of read failure")) <= 0)
664+
(OK || PQprotocolVersion(conn) < 3) ? NULL :
665+
_("aborted because of read failure")) <= 0)
671666
OK = false;
672667

673668
copyin_cleanup:
674669

675670
/*
676-
* Check command status and return to normal libpq state
671+
* Check command status and return to normal libpq state.
677672
*
678-
* We must not ever return with the status still PGRES_COPY_IN. Our
679-
* caller is unable to distinguish that situation from reaching the next
680-
* COPY in a command string that happened to contain two consecutive COPY
681-
* FROM STDIN commands. XXX if something makes PQputCopyEnd() fail
682-
* indefinitely while retaining status PGRES_COPY_IN, we get an infinite
683-
* loop. This is more realistic than handleCopyOut()'s counterpart risk.
673+
* We do not want to return with the status still PGRES_COPY_IN: our
674+
* caller would be unable to distinguish that situation from reaching the
675+
* next COPY in a command string that happened to contain two consecutive
676+
* COPY FROM STDIN commands. We keep trying PQputCopyEnd() in the hope
677+
* it'll work eventually. (What's actually likely to happen is that in
678+
* attempting to flush the data, libpq will eventually realize that the
679+
* connection is lost. But that's fine; it will get us out of COPY_IN
680+
* state, which is what we need.)
684681
*/
685682
while (res = PQgetResult(conn), PQresultStatus(res) == PGRES_COPY_IN)
686683
{
687684
OK = false;
688685
PQclear(res);
689-
690-
PQputCopyEnd(pset.db, _("trying to exit copy mode"));
686+
/* We can't send an error message if we're using protocol version 2 */
687+
PQputCopyEnd(conn,
688+
(PQprotocolVersion(conn) < 3) ? NULL :
689+
_("trying to exit copy mode"));
691690
}
692691
if (PQresultStatus(res) != PGRES_COMMAND_OK)
693692
{

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