Hi, hackers. psql \copy command can stream data from client host just like the normal copy command can do on server host. Let's assume we want to stream a local data file from psql: pgsql =# \copy tbl from '/tmp/datafile' (format 'text'); If there's error inside the data file, \copy will still stream the whole data file before it reports the error. This is undesirable If the data file is very large, or it's an infinite pipe. The normal copy command which reads file on the server host can report error immediately as expected.
The problem seems to be pqParseInput3(). When error occurs in server backend, it will send 'E' packet back to client. During \copy command, the connection's asyncStatus is PGASYNC_COPY_IN, any 'E' packet will get ignored by this path: else if (conn->asyncStatus != PGASYNC_BUSY) { /* If not IDLE state, just wait ... */ if (conn->asyncStatus != PGASYNC_IDLE) return; So the client can't detect the error sent back by server. I've attached a patch to demonstrate one way to workaround this. Save the error via pqGetErrorNotice3() if the conn is PGASYNC_COPY_IN status. The client code(psql) can detect the error via PQerrorMessage(). Probably still lots of details need to be considered but should be good enough to start this discussion. Any thoughts on this issue? Best regards Peifeng Qiu
From a155adf6c1e6a6c31cf4146ce53827180247f384 Mon Sep 17 00:00:00 2001 From: Peifeng Qiu <pg...@qiupf.dev> Date: Thu, 29 Dec 2022 11:37:19 +0900 Subject: [PATCH] psql: stop at error immediately during \copy --- src/bin/psql/copy.c | 7 +++++++ src/interfaces/libpq/fe-protocol3.c | 13 +++++++++++++ 2 files changed, 20 insertions(+) diff --git a/src/bin/psql/copy.c b/src/bin/psql/copy.c index 0f66ebc2ed..1d285312c2 100644 --- a/src/bin/psql/copy.c +++ b/src/bin/psql/copy.c @@ -580,6 +580,7 @@ handleCopyIn(PGconn *conn, FILE *copystream, bool isbinary, PGresult **res) bool copydone = false; int buflen; bool at_line_begin = true; + char *err; /* * In text mode, we have to read the input one line at a time, so that @@ -660,6 +661,12 @@ handleCopyIn(PGconn *conn, FILE *copystream, bool isbinary, PGresult **res) OK = false; break; } + err = PQerrorMessage(conn); + if (err && *err) + { + /* We got error from server backend. Stop processing. */ + break; + } buflen = 0; } diff --git a/src/interfaces/libpq/fe-protocol3.c b/src/interfaces/libpq/fe-protocol3.c index 364bad2b88..b50ce7da2e 100644 --- a/src/interfaces/libpq/fe-protocol3.c +++ b/src/interfaces/libpq/fe-protocol3.c @@ -150,6 +150,19 @@ pqParseInput3(PGconn *conn) if (pqGetErrorNotice3(conn, false)) return; } + else if (conn->asyncStatus == PGASYNC_COPY_IN) + { + /* + * Process and save error message during COPY. Client can check + * this error via PQerrorMessage. + */ + if (id == 'E') + { + if (pqGetErrorNotice3(conn, true)) + return; + break; + } + } else if (conn->asyncStatus != PGASYNC_BUSY) { /* If not IDLE state, just wait ... */ -- 2.39.0