diff --git a/doc/src/sgml/ref/copy.sgml b/doc/src/sgml/ref/copy.sgml
index 5be3514612e..0544c68bbc0 100644
--- a/doc/src/sgml/ref/copy.sgml
+++ b/doc/src/sgml/ref/copy.sgml
@@ -370,6 +370,16 @@ COPY count
The count is the number
of rows copied.
+
+
+
+ psql> will print this command tag only if the command
+ was not COPY ... TO STDOUT>, or the
+ equivalent psql> meta-command
+ \copy ... to stdout>. This is to prevent confusing the
+ command tag with the data that was just printed.
+
+
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 8813be8f2a2..5dce06af26e 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -863,36 +863,36 @@ testdb=>
When program> is specified,
command is
- executed by psql and the data from
+ executed by psql and the data passed from
or to command is
routed between the server and the client.
- This means that the execution privileges are those of
+ Again, the execution privileges are those of
the local user, not the server, and no SQL superuser
privileges are required.
- \copy ... from stdin | to stdout
- reads/writes based on the command input and output respectively.
- All rows are read from the same source that issued the command,
- continuing until \. is read or the stream
- reaches EOF>. Output is sent to the same place as
- command output. To read/write from
- psql's standard input or output, use
- pstdin> or pstdout>. This option is useful
+
+ For \copy ... from stdin>, data rows are read from the same
+ source that issued the command, continuing until \.
+ is read or the stream reaches EOF>. This option is useful
for populating tables in-line within a SQL script file.
+ For \copy ... to stdout>, output is sent to the same place
+ as psql> command output, and
+ the COPY count> command status is
+ not printed (since it might be confused with a data row).
+ To read/write psql's standard input or
+ output regardless of the current command source or \o>
+ option, write from pstdin> or to pstdout>.
- The syntax of the command is similar to that of the
+ The syntax of this command is similar to that of the
SQL
- command, and
- option
- must indicate one of the options of the
- SQL command.
- Note that, because of this,
- special parsing rules apply to the \copy
- command. In particular, the variable substitution rules and
- backslash escapes do not apply.
+ command. All options other than the data source/destination are
+ as specified for .
+ Because of this, special parsing rules apply to the \copy>
+ command. In particular, psql>'s variable substitution
+ rules and backslash escapes do not apply.
diff --git a/src/bin/psql/common.c b/src/bin/psql/common.c
index 6ca9bbc9d86..6968adfd42c 100644
--- a/src/bin/psql/common.c
+++ b/src/bin/psql/common.c
@@ -632,7 +632,9 @@ StoreQueryTuple(const PGresult *result)
* degenerates to an AcceptResult() call.
*
* Changes its argument to point to the last PGresult of the command string,
- * or NULL if that result was for a COPY FROM STDIN or COPY TO STDOUT.
+ * or NULL if that result was for a COPY TO STDOUT. (Returning NULL prevents
+ * the command status from being printed, which we want in that case so that
+ * the status line doesn't get taken as part of the COPY data.)
*
* Returns true on complete success, false otherwise. Possible failure modes
* include purely client-side problems; check the transaction status for the
@@ -641,14 +643,14 @@ StoreQueryTuple(const PGresult *result)
static bool
ProcessResult(PGresult **results)
{
- PGresult *next_result;
bool success = true;
bool first_cycle = true;
- do
+ for (;;)
{
ExecStatusType result_status;
bool is_copy;
+ PGresult *next_result;
if (!AcceptResult(*results))
{
@@ -693,6 +695,7 @@ ProcessResult(PGresult **results)
* otherwise use queryFout or cur_cmd_source as appropriate.
*/
FILE *copystream = pset.copyStream;
+ PGresult *copy_result;
SetCancelConn();
if (result_status == PGRES_COPY_OUT)
@@ -700,7 +703,19 @@ ProcessResult(PGresult **results)
if (!copystream)
copystream = pset.queryFout;
success = handleCopyOut(pset.db,
- copystream) && success;
+ copystream,
+ ©_result) && success;
+
+ /*
+ * Suppress status printing if the report would go to the same
+ * place as the COPY data just went. Note this doesn't
+ * prevent error reporting, since handleCopyOut did that.
+ */
+ if (copystream == pset.queryFout)
+ {
+ PQclear(copy_result);
+ copy_result = NULL;
+ }
}
else
{
@@ -708,30 +723,37 @@ ProcessResult(PGresult **results)
copystream = pset.cur_cmd_source;
success = handleCopyIn(pset.db,
copystream,
- PQbinaryTuples(*results)) && success;
+ PQbinaryTuples(*results),
+ ©_result) && success;
}
ResetCancelConn();
/*
- * Call PQgetResult() once more. In the typical case of a
- * single-command string, it will return NULL. Otherwise, we'll
- * have other results to process that may include other COPYs.
+ * Replace the PGRES_COPY_OUT/IN result with COPY command's exit
+ * status, or with NULL if we want to suppress printing anything.
*/
PQclear(*results);
- *results = next_result = PQgetResult(pset.db);
+ *results = copy_result;
}
else if (first_cycle)
+ {
/* fast path: no COPY commands; PQexec visited all results */
break;
- else if ((next_result = PQgetResult(pset.db)))
- {
- /* non-COPY command(s) after a COPY: keep the last one */
- PQclear(*results);
- *results = next_result;
}
+ /*
+ * Check PQgetResult() again. In the typical case of a single-command
+ * string, it will return NULL. Otherwise, we'll have other results
+ * to process that may include other COPYs. We keep the last result.
+ */
+ next_result = PQgetResult(pset.db);
+ if (!next_result)
+ break;
+
+ PQclear(*results);
+ *results = next_result;
first_cycle = false;
- } while (next_result);
+ }
/* may need this to recover from conn loss during COPY */
if (!first_cycle && !CheckConnection())
diff --git a/src/bin/psql/copy.c b/src/bin/psql/copy.c
index a058f2ff0d4..d706206cc96 100644
--- a/src/bin/psql/copy.c
+++ b/src/bin/psql/copy.c
@@ -429,16 +429,17 @@ do_copy(const char *args)
* conn should be a database connection that you just issued COPY TO on
* and got back a PGRES_COPY_OUT result.
* copystream is the file stream for the data to go to.
+ * The final status for the COPY is returned into *res (but note
+ * we already reported the error, if it's not a success result).
*
* result is true if successful, false if not.
*/
bool
-handleCopyOut(PGconn *conn, FILE *copystream)
+handleCopyOut(PGconn *conn, FILE *copystream, PGresult **res)
{
bool OK = true;
char *buf;
int ret;
- PGresult *res;
for (;;)
{
@@ -485,13 +486,12 @@ handleCopyOut(PGconn *conn, FILE *copystream)
* but hasn't exited COPY_OUT state internally. So we ignore the
* possibility here.
*/
- res = PQgetResult(conn);
- if (PQresultStatus(res) != PGRES_COMMAND_OK)
+ *res = PQgetResult(conn);
+ if (PQresultStatus(*res) != PGRES_COMMAND_OK)
{
psql_error("%s", PQerrorMessage(conn));
OK = false;
}
- PQclear(res);
return OK;
}
@@ -504,6 +504,8 @@ handleCopyOut(PGconn *conn, FILE *copystream)
* and got back a PGRES_COPY_IN result.
* copystream is the file stream to read the data from.
* isbinary can be set from PQbinaryTuples().
+ * The final status for the COPY is returned into *res (but note
+ * we already reported the error, if it's not a success result).
*
* result is true if successful, false if not.
*/
@@ -512,12 +514,11 @@ handleCopyOut(PGconn *conn, FILE *copystream)
#define COPYBUFSIZ 8192
bool
-handleCopyIn(PGconn *conn, FILE *copystream, bool isbinary)
+handleCopyIn(PGconn *conn, FILE *copystream, bool isbinary, PGresult **res)
{
bool OK;
const char *prompt;
char buf[COPYBUFSIZ];
- PGresult *res;
/*
* Establish longjmp destination for exiting from wait-for-input. (This is
@@ -679,21 +680,20 @@ copyin_cleanup:
* connection is lost. But that's fine; it will get us out of COPY_IN
* state, which is what we need.)
*/
- while (res = PQgetResult(conn), PQresultStatus(res) == PGRES_COPY_IN)
+ while (*res = PQgetResult(conn), PQresultStatus(*res) == PGRES_COPY_IN)
{
OK = false;
- PQclear(res);
+ PQclear(*res);
/* We can't send an error message if we're using protocol version 2 */
PQputCopyEnd(conn,
(PQprotocolVersion(conn) < 3) ? NULL :
_("trying to exit copy mode"));
}
- if (PQresultStatus(res) != PGRES_COMMAND_OK)
+ if (PQresultStatus(*res) != PGRES_COMMAND_OK)
{
psql_error("%s", PQerrorMessage(conn));
OK = false;
}
- PQclear(res);
return OK;
}
diff --git a/src/bin/psql/copy.h b/src/bin/psql/copy.h
index ec1f0d09a9a..2c71da00603 100644
--- a/src/bin/psql/copy.h
+++ b/src/bin/psql/copy.h
@@ -12,11 +12,13 @@
/* handler for \copy */
-bool do_copy(const char *args);
+extern bool do_copy(const char *args);
/* lower level processors for copy in/out streams */
-bool handleCopyOut(PGconn *conn, FILE *copystream);
-bool handleCopyIn(PGconn *conn, FILE *copystream, bool isbinary);
+extern bool handleCopyOut(PGconn *conn, FILE *copystream,
+ PGresult **res);
+extern bool handleCopyIn(PGconn *conn, FILE *copystream, bool isbinary,
+ PGresult **res);
#endif