Commit 6d3ede5f authored by Tom Lane's avatar Tom Lane

Fix psql's "\g target" meta-command to work with COPY TO STDOUT.

Previously, \g would successfully execute the COPY command, but
the target specification if any was ignored, so that the data was
always dumped to the regular query output target.  This seems like
a clear bug, so let's not just fix it but back-patch it.

While at it, adjust the documentation for \copy to recommend
"COPY ... TO STDOUT \g foo" as a plausible alternative.

Back-patch to 9.5.  The problem exists much further back, but the
code associated with \g was refactored enough in 9.5 that we'd
need a significantly different patch for 9.4, and it doesn't
seem worth the trouble.

Daniel Vérité, reviewed by Fabien Coelho

Discussion: https://postgr.es/m/15dadc39-e050-4d46-956b-dcc4ed098753@manitou-mail.org
parent 1e4730c6
...@@ -1037,10 +1037,24 @@ testdb=> ...@@ -1037,10 +1037,24 @@ testdb=>
<tip> <tip>
<para> <para>
This operation is not as efficient as the <acronym>SQL</acronym> Another way to obtain the same result as <literal>\copy
<command>COPY</command> command because all data must pass ... to</literal> is to use the <acronym>SQL</acronym> <literal>COPY
through the client/server connection. For large ... TO STDOUT</literal> command and terminate it
amounts of data the <acronym>SQL</acronym> command might be preferable. with <literal>\g <replaceable>filename</replaceable></literal>
or <literal>\g |<replaceable>program</replaceable></literal>.
Unlike <literal>\copy</literal>, this method allows the command to
span multiple lines; also, variable interpolation and backquote
expansion can be used.
</para>
</tip>
<tip>
<para>
These operations are not as efficient as the <acronym>SQL</acronym>
<command>COPY</command> command with a file or program data source or
destination, because all data must pass through the client/server
connection. For large amounts of data the <acronym>SQL</acronym>
command might be preferable.
</para> </para>
</tip> </tip>
......
...@@ -1092,20 +1092,49 @@ ProcessResult(PGresult **results) ...@@ -1092,20 +1092,49 @@ ProcessResult(PGresult **results)
* connection out of its COPY state, then call PQresultStatus() * connection out of its COPY state, then call PQresultStatus()
* once and report any error. * once and report any error.
* *
* If pset.copyStream is set, use that as data source/sink, * For COPY OUT, direct the output to pset.copyStream if it's set,
* otherwise use queryFout or cur_cmd_source as appropriate. * otherwise to pset.gfname if it's set, otherwise to queryFout.
* For COPY IN, use pset.copyStream as data source if it's set,
* otherwise cur_cmd_source.
*/ */
FILE *copystream = pset.copyStream; FILE *copystream;
PGresult *copy_result; PGresult *copy_result;
SetCancelConn(); SetCancelConn();
if (result_status == PGRES_COPY_OUT) if (result_status == PGRES_COPY_OUT)
{ {
if (!copystream) bool need_close = false;
bool is_pipe = false;
if (pset.copyStream)
{
/* invoked by \copy */
copystream = pset.copyStream;
}
else if (pset.gfname)
{
/* invoked by \g */
if (openQueryOutputFile(pset.gfname,
&copystream, &is_pipe))
{
need_close = true;
if (is_pipe)
disable_sigpipe_trap();
}
else
copystream = NULL; /* discard COPY data entirely */
}
else
{
/* fall back to the generic query output stream */
copystream = pset.queryFout; copystream = pset.queryFout;
}
success = handleCopyOut(pset.db, success = handleCopyOut(pset.db,
copystream, copystream,
&copy_result) && success; &copy_result)
&& success
&& (copystream != NULL);
/* /*
* Suppress status printing if the report would go to the same * Suppress status printing if the report would go to the same
...@@ -1117,11 +1146,25 @@ ProcessResult(PGresult **results) ...@@ -1117,11 +1146,25 @@ ProcessResult(PGresult **results)
PQclear(copy_result); PQclear(copy_result);
copy_result = NULL; copy_result = NULL;
} }
if (need_close)
{
/* close \g argument file/pipe */
if (is_pipe)
{
pclose(copystream);
restore_sigpipe_trap();
}
else
{
fclose(copystream);
}
}
} }
else else
{ {
if (!copystream) /* COPY IN */
copystream = pset.cur_cmd_source; copystream = pset.copyStream ? pset.copyStream : pset.cur_cmd_source;
success = handleCopyIn(pset.db, success = handleCopyIn(pset.db,
copystream, copystream,
PQbinaryTuples(*results), PQbinaryTuples(*results),
......
...@@ -425,7 +425,10 @@ do_copy(const char *args) ...@@ -425,7 +425,10 @@ do_copy(const char *args)
* *
* conn should be a database connection that you just issued COPY TO on * conn should be a database connection that you just issued COPY TO on
* and got back a PGRES_COPY_OUT result. * and got back a PGRES_COPY_OUT result.
*
* copystream is the file stream for the data to go to. * copystream is the file stream for the data to go to.
* copystream can be NULL to eat the data without writing it anywhere.
*
* The final status for the COPY is returned into *res (but note * The final status for the COPY is returned into *res (but note
* we already reported the error, if it's not a success result). * we already reported the error, if it's not a success result).
* *
...@@ -447,7 +450,7 @@ handleCopyOut(PGconn *conn, FILE *copystream, PGresult **res) ...@@ -447,7 +450,7 @@ handleCopyOut(PGconn *conn, FILE *copystream, PGresult **res)
if (buf) if (buf)
{ {
if (OK && fwrite(buf, 1, ret, copystream) != ret) if (OK && copystream && fwrite(buf, 1, ret, copystream) != ret)
{ {
psql_error("could not write COPY data: %s\n", psql_error("could not write COPY data: %s\n",
strerror(errno)); strerror(errno));
...@@ -458,7 +461,7 @@ handleCopyOut(PGconn *conn, FILE *copystream, PGresult **res) ...@@ -458,7 +461,7 @@ handleCopyOut(PGconn *conn, FILE *copystream, PGresult **res)
} }
} }
if (OK && fflush(copystream)) if (OK && copystream && fflush(copystream))
{ {
psql_error("could not write COPY data: %s\n", psql_error("could not write COPY data: %s\n",
strerror(errno)); strerror(errno));
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment