]> granicus.if.org Git - postgresql/commitdiff
Fix psql's "\g target" meta-command to work with COPY TO STDOUT.
authorTom Lane <tgl@sss.pgh.pa.us>
Sat, 26 Jan 2019 19:15:42 +0000 (14:15 -0500)
committerTom Lane <tgl@sss.pgh.pa.us>
Sat, 26 Jan 2019 19:15:42 +0000 (14:15 -0500)
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

doc/src/sgml/ref/psql-ref.sgml
src/bin/psql/common.c
src/bin/psql/copy.c

index 6c76cf2f00180cc8fa0a3106701b4ca02b41fc85..d7539ae743948b642d35a83fd8ed28f220db9335 100644 (file)
@@ -1037,10 +1037,24 @@ testdb=&gt;
 
         <tip>
         <para>
-        This operation is not as efficient as the <acronym>SQL</acronym>
-        <command>COPY</command> command because all data must pass
-        through the client/server connection. For large
-        amounts of data the <acronym>SQL</acronym> command might be preferable.
+        Another way to obtain the same result as <literal>\copy
+        ... to</literal> is to use the <acronym>SQL</acronym> <literal>COPY
+        ... TO STDOUT</literal> command and terminate it
+        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>
         </tip>
 
index b11d7ac6ce029b1febe2f2cc63dbff70eaadc4a6..5d8634d8186d72fe8403082343da699a2a2e377d 100644 (file)
@@ -1092,20 +1092,49 @@ ProcessResult(PGresult **results)
                         * connection out of its COPY state, then call PQresultStatus()
                         * once and report any error.
                         *
-                        * If pset.copyStream is set, use that as data source/sink,
-                        * otherwise use queryFout or cur_cmd_source as appropriate.
+                        * For COPY OUT, direct the output to pset.copyStream if it's set,
+                        * 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;
 
                        SetCancelConn();
                        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;
+                               }
+
                                success = handleCopyOut(pset.db,
                                                                                copystream,
-                                                                               &copy_result) && success;
+                                                                               &copy_result)
+                                       && success
+                                       && (copystream != NULL);
 
                                /*
                                 * Suppress status printing if the report would go to the same
@@ -1117,11 +1146,25 @@ ProcessResult(PGresult **results)
                                        PQclear(copy_result);
                                        copy_result = NULL;
                                }
+
+                               if (need_close)
+                               {
+                                       /* close \g argument file/pipe */
+                                       if (is_pipe)
+                                       {
+                                               pclose(copystream);
+                                               restore_sigpipe_trap();
+                                       }
+                                       else
+                                       {
+                                               fclose(copystream);
+                                       }
+                               }
                        }
                        else
                        {
-                               if (!copystream)
-                                       copystream = pset.cur_cmd_source;
+                               /* COPY IN */
+                               copystream = pset.copyStream ? pset.copyStream : pset.cur_cmd_source;
                                success = handleCopyIn(pset.db,
                                                                           copystream,
                                                                           PQbinaryTuples(*results),
index 02c85119fea662b6707d379d238d623a021af9d2..18a8c8eebb549599ef89968b423d685572425a2e 100644 (file)
@@ -425,7 +425,10 @@ 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.
+ * copystream can be NULL to eat the data without writing it anywhere.
+ *
  * The final status for the COPY is returned into *res (but note
  * we already reported the error, if it's not a success result).
  *
@@ -447,7 +450,7 @@ handleCopyOut(PGconn *conn, FILE *copystream, PGresult **res)
 
                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",
                                                   strerror(errno));
@@ -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",
                                   strerror(errno));