Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit 4a79fd1

Browse files
committed
Fix psql's behavior with \g for a multiple-command string.
The pre-v15 behavior was to discard all but the last result, but with the new behavior of printing all results by default, we will send each such result to the \g file. However, we're still opening and closing the \g file for each result, so you lose all but the last result anyway. Move the output-file state up to ExecQueryAndProcessResults so that we open/close the \g file only once per command string. To support this without changing other behavior, we must adjust PrintQueryResult to have separate FILE * arguments for query and status output (since status output has never gone to the \g file). That in turn makes it a good idea to push the responsibility for fflush'ing output down to PrintQueryTuples and PrintQueryStatus. Also fix an infinite loop if COPY IN/OUT is attempted in \watch. We used to reject that, but that error exit path got broken somewhere along the line in v15. There seems no real reason to reject it anyway as the code now stands, so just remove the error exit and make sure that COPY OUT data goes to the right place. Also remove PrintQueryResult's unused is_watch parameter, and make some other cosmetic cleanups (adjust obsolete comments, break some overly-long lines). Daniel Vérité and Tom Lane Discussion: https://postgr.es/m/4333844c-2244-4d6e-a49a-1d483fbe304f@manitou-mail.org
1 parent 0ae5db2 commit 4a79fd1

File tree

1 file changed

+135
-103
lines changed

1 file changed

+135
-103
lines changed

src/bin/psql/common.c

Lines changed: 135 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,12 @@
3232

3333
static bool DescribeQuery(const char *query, double *elapsed_msec);
3434
static bool ExecQueryUsingCursor(const char *query, double *elapsed_msec);
35-
static int ExecQueryAndProcessResults(const char *query, double *elapsed_msec, bool *svpt_gone_p,
36-
bool is_watch, const printQueryOpt *opt, FILE *printQueryFout);
35+
static int ExecQueryAndProcessResults(const char *query,
36+
double *elapsed_msec,
37+
bool *svpt_gone_p,
38+
bool is_watch,
39+
const printQueryOpt *opt,
40+
FILE *printQueryFout);
3741
static bool command_no_begin(const char *query);
3842
static bool is_select_command(const char *query);
3943

@@ -662,49 +666,27 @@ PrintNotifications(void)
662666
/*
663667
* PrintQueryTuples: assuming query result is OK, print its tuples
664668
*
669+
* We use the options given by opt unless that's NULL, in which case
670+
* we use pset.popt.
671+
*
672+
* Output is to printQueryFout unless that's NULL, in which case
673+
* we use pset.queryFout.
674+
*
665675
* Returns true if successful, false otherwise.
666676
*/
667677
static bool
668-
PrintQueryTuples(const PGresult *result, const printQueryOpt *opt, FILE *printQueryFout)
678+
PrintQueryTuples(const PGresult *result, const printQueryOpt *opt,
679+
FILE *printQueryFout)
669680
{
670681
bool ok = true;
682+
FILE *fout = printQueryFout ? printQueryFout : pset.queryFout;
671683

672-
/* write output to \g argument, if any */
673-
if (pset.gfname)
684+
printQuery(result, opt ? opt : &pset.popt, fout, false, pset.logfile);
685+
fflush(fout);
686+
if (ferror(fout))
674687
{
675-
FILE *fout;
676-
bool is_pipe;
677-
678-
if (!openQueryOutputFile(pset.gfname, &fout, &is_pipe))
679-
return false;
680-
if (is_pipe)
681-
disable_sigpipe_trap();
682-
683-
printQuery(result, &pset.popt, fout, false, pset.logfile);
684-
if (ferror(fout))
685-
{
686-
pg_log_error("could not print result table: %m");
687-
ok = false;
688-
}
689-
690-
if (is_pipe)
691-
{
692-
pclose(fout);
693-
restore_sigpipe_trap();
694-
}
695-
else
696-
fclose(fout);
697-
}
698-
else
699-
{
700-
FILE *fout = printQueryFout ? printQueryFout : pset.queryFout;
701-
702-
printQuery(result, opt ? opt : &pset.popt, fout, false, pset.logfile);
703-
if (ferror(fout))
704-
{
705-
pg_log_error("could not print result table: %m");
706-
ok = false;
707-
}
688+
pg_log_error("could not print result table: %m");
689+
ok = false;
708690
}
709691

710692
return ok;
@@ -845,26 +827,24 @@ ExecQueryTuples(const PGresult *result)
845827

846828

847829
/*
848-
* Marshal the COPY data. Either subroutine will get the
830+
* Marshal the COPY data. Either path will get the
849831
* connection out of its COPY state, then call PQresultStatus()
850832
* once and report any error. Return whether all was ok.
851833
*
852-
* For COPY OUT, direct the output to pset.copyStream if it's set,
853-
* otherwise to pset.gfname if it's set, otherwise to queryFout.
834+
* For COPY OUT, direct the output to copystream, or discard if that's NULL.
854835
* For COPY IN, use pset.copyStream as data source if it's set,
855836
* otherwise cur_cmd_source.
856837
*
857-
* Update result if further processing is necessary, or NULL otherwise.
838+
* Update *resultp if further processing is necessary; set to NULL otherwise.
858839
* Return a result when queryFout can safely output a result status: on COPY
859840
* IN, or on COPY OUT if written to something other than pset.queryFout.
860841
* Returning NULL prevents the command status from being printed, which we
861842
* want if the status line doesn't get taken as part of the COPY data.
862843
*/
863844
static bool
864-
HandleCopyResult(PGresult **resultp)
845+
HandleCopyResult(PGresult **resultp, FILE *copystream)
865846
{
866847
bool success;
867-
FILE *copystream;
868848
PGresult *copy_result;
869849
ExecStatusType result_status = PQresultStatus(*resultp);
870850

@@ -875,33 +855,6 @@ HandleCopyResult(PGresult **resultp)
875855

876856
if (result_status == PGRES_COPY_OUT)
877857
{
878-
bool need_close = false;
879-
bool is_pipe = false;
880-
881-
if (pset.copyStream)
882-
{
883-
/* invoked by \copy */
884-
copystream = pset.copyStream;
885-
}
886-
else if (pset.gfname)
887-
{
888-
/* invoked by \g */
889-
if (openQueryOutputFile(pset.gfname,
890-
&copystream, &is_pipe))
891-
{
892-
need_close = true;
893-
if (is_pipe)
894-
disable_sigpipe_trap();
895-
}
896-
else
897-
copystream = NULL; /* discard COPY data entirely */
898-
}
899-
else
900-
{
901-
/* fall back to the generic query output stream */
902-
copystream = pset.queryFout;
903-
}
904-
905858
success = handleCopyOut(pset.db,
906859
copystream,
907860
&copy_result)
@@ -917,24 +870,11 @@ HandleCopyResult(PGresult **resultp)
917870
PQclear(copy_result);
918871
copy_result = NULL;
919872
}
920-
921-
if (need_close)
922-
{
923-
/* close \g argument file/pipe */
924-
if (is_pipe)
925-
{
926-
pclose(copystream);
927-
restore_sigpipe_trap();
928-
}
929-
else
930-
{
931-
fclose(copystream);
932-
}
933-
}
934873
}
935874
else
936875
{
937876
/* COPY IN */
877+
/* Ignore the copystream argument passed to the function */
938878
copystream = pset.copyStream ? pset.copyStream : pset.cur_cmd_source;
939879
success = handleCopyIn(pset.db,
940880
copystream,
@@ -974,6 +914,7 @@ PrintQueryStatus(PGresult *result, FILE *printQueryFout)
974914
}
975915
else
976916
fprintf(fout, "%s\n", PQcmdStatus(result));
917+
fflush(fout);
977918
}
978919

979920
if (pset.logfile)
@@ -989,10 +930,16 @@ PrintQueryStatus(PGresult *result, FILE *printQueryFout)
989930
*
990931
* Note: Utility function for use by SendQuery() only.
991932
*
933+
* last is true if this is the last result of a command string.
934+
* opt and printQueryFout are defined as for PrintQueryTuples.
935+
* printStatusFout is where to send command status; NULL means pset.queryFout.
936+
*
992937
* Returns true if the query executed successfully, false otherwise.
993938
*/
994939
static bool
995-
PrintQueryResult(PGresult *result, bool last, bool is_watch, const printQueryOpt *opt, FILE *printQueryFout)
940+
PrintQueryResult(PGresult *result, bool last,
941+
const printQueryOpt *opt, FILE *printQueryFout,
942+
FILE *printStatusFout)
996943
{
997944
bool success;
998945
const char *cmdstatus;
@@ -1022,14 +969,14 @@ PrintQueryResult(PGresult *result, bool last, bool is_watch, const printQueryOpt
1022969
if (strncmp(cmdstatus, "INSERT", 6) == 0 ||
1023970
strncmp(cmdstatus, "UPDATE", 6) == 0 ||
1024971
strncmp(cmdstatus, "DELETE", 6) == 0)
1025-
PrintQueryStatus(result, printQueryFout);
972+
PrintQueryStatus(result, printStatusFout);
1026973
}
1027974

1028975
break;
1029976

1030977
case PGRES_COMMAND_OK:
1031978
if (last || pset.show_all_results)
1032-
PrintQueryStatus(result, printQueryFout);
979+
PrintQueryStatus(result, printStatusFout);
1033980
success = true;
1034981
break;
1035982

@@ -1056,8 +1003,6 @@ PrintQueryResult(PGresult *result, bool last, bool is_watch, const printQueryOpt
10561003
break;
10571004
}
10581005

1059-
fflush(printQueryFout ? printQueryFout : pset.queryFout);
1060-
10611006
return success;
10621007
}
10631008

@@ -1399,7 +1344,7 @@ DescribeQuery(const char *query, double *elapsed_msec)
13991344
}
14001345

14011346
if (OK && result)
1402-
OK = PrintQueryResult(result, true, false, NULL, NULL);
1347+
OK = PrintQueryResult(result, true, NULL, NULL, NULL);
14031348

14041349
termPQExpBuffer(&buf);
14051350
}
@@ -1421,10 +1366,9 @@ DescribeQuery(const char *query, double *elapsed_msec)
14211366
*
14221367
* Sends query and cycles through PGresult objects.
14231368
*
1424-
* When not under \watch and if our command string contained a COPY FROM STDIN
1425-
* or COPY TO STDOUT, the PGresult associated with these commands must be
1426-
* processed by providing an input or output stream. In that event, we'll
1427-
* marshal data for the COPY.
1369+
* If our command string contained a COPY FROM STDIN or COPY TO STDOUT, the
1370+
* PGresult associated with these commands must be processed by providing an
1371+
* input or output stream. In that event, we'll marshal data for the COPY.
14281372
*
14291373
* For other commands, the results are processed normally, depending on their
14301374
* status.
@@ -1437,14 +1381,18 @@ DescribeQuery(const char *query, double *elapsed_msec)
14371381
* committed.
14381382
*/
14391383
static int
1440-
ExecQueryAndProcessResults(const char *query, double *elapsed_msec, bool *svpt_gone_p,
1441-
bool is_watch, const printQueryOpt *opt, FILE *printQueryFout)
1384+
ExecQueryAndProcessResults(const char *query,
1385+
double *elapsed_msec, bool *svpt_gone_p,
1386+
bool is_watch,
1387+
const printQueryOpt *opt, FILE *printQueryFout)
14421388
{
14431389
bool timing = pset.timing;
14441390
bool success;
14451391
instr_time before,
14461392
after;
14471393
PGresult *result;
1394+
FILE *gfile_fout = NULL;
1395+
bool gfile_is_pipe = false;
14481396

14491397
if (timing)
14501398
INSTR_TIME_SET_CURRENT(before);
@@ -1555,14 +1503,57 @@ ExecQueryAndProcessResults(const char *query, double *elapsed_msec, bool *svpt_g
15551503
if (result_status == PGRES_COPY_IN ||
15561504
result_status == PGRES_COPY_OUT)
15571505
{
1558-
if (is_watch)
1506+
FILE *copy_stream = NULL;
1507+
1508+
/*
1509+
* For COPY OUT, direct the output to the default place (probably
1510+
* a pager pipe) for \watch, or to pset.copyStream for \copy,
1511+
* otherwise to pset.gfname if that's set, otherwise to
1512+
* pset.queryFout.
1513+
*/
1514+
if (result_status == PGRES_COPY_OUT)
15591515
{
1560-
ClearOrSaveAllResults();
1561-
pg_log_error("\\watch cannot be used with COPY");
1562-
return -1;
1516+
if (is_watch)
1517+
{
1518+
/* invoked by \watch */
1519+
copy_stream = printQueryFout ? printQueryFout : pset.queryFout;
1520+
}
1521+
else if (pset.copyStream)
1522+
{
1523+
/* invoked by \copy */
1524+
copy_stream = pset.copyStream;
1525+
}
1526+
else if (pset.gfname)
1527+
{
1528+
/* send to \g file, which we may have opened already */
1529+
if (gfile_fout == NULL)
1530+
{
1531+
if (openQueryOutputFile(pset.gfname,
1532+
&gfile_fout, &gfile_is_pipe))
1533+
{
1534+
if (gfile_is_pipe)
1535+
disable_sigpipe_trap();
1536+
copy_stream = gfile_fout;
1537+
}
1538+
else
1539+
success = false;
1540+
}
1541+
else
1542+
copy_stream = gfile_fout;
1543+
}
1544+
else
1545+
{
1546+
/* fall back to the generic query output stream */
1547+
copy_stream = pset.queryFout;
1548+
}
15631549
}
15641550

1565-
success &= HandleCopyResult(&result);
1551+
/*
1552+
* Even if the output stream could not be opened, we call
1553+
* HandleCopyResult() with a NULL output stream to collect and
1554+
* discard the COPY data.
1555+
*/
1556+
success &= HandleCopyResult(&result, copy_stream);
15661557
}
15671558

15681559
/*
@@ -1594,7 +1585,36 @@ ExecQueryAndProcessResults(const char *query, double *elapsed_msec, bool *svpt_g
15941585

15951586
/* this may or may not print something depending on settings */
15961587
if (result != NULL)
1597-
success &= PrintQueryResult(result, last, false, opt, printQueryFout);
1588+
{
1589+
/*
1590+
* If results need to be printed into the file specified by \g,
1591+
* open it, unless we already did. Note that when pset.gfname is
1592+
* set, the passed-in value of printQueryFout is not used for
1593+
* tuple output, but it's still used for status output.
1594+
*/
1595+
FILE *tuples_fout = printQueryFout;
1596+
bool do_print = true;
1597+
1598+
if (PQresultStatus(result) == PGRES_TUPLES_OK &&
1599+
pset.gfname)
1600+
{
1601+
if (gfile_fout == NULL)
1602+
{
1603+
if (openQueryOutputFile(pset.gfname,
1604+
&gfile_fout, &gfile_is_pipe))
1605+
{
1606+
if (gfile_is_pipe)
1607+
disable_sigpipe_trap();
1608+
}
1609+
else
1610+
success = do_print = false;
1611+
}
1612+
tuples_fout = gfile_fout;
1613+
}
1614+
if (do_print)
1615+
success &= PrintQueryResult(result, last, opt,
1616+
tuples_fout, printQueryFout);
1617+
}
15981618

15991619
/* set variables on last result if all went well */
16001620
if (!is_watch && last && success)
@@ -1610,6 +1630,18 @@ ExecQueryAndProcessResults(const char *query, double *elapsed_msec, bool *svpt_g
16101630
}
16111631
}
16121632

1633+
/* close \g file if we opened it */
1634+
if (gfile_fout)
1635+
{
1636+
if (gfile_is_pipe)
1637+
{
1638+
pclose(gfile_fout);
1639+
restore_sigpipe_trap();
1640+
}
1641+
else
1642+
fclose(gfile_fout);
1643+
}
1644+
16131645
/* may need this to recover from conn loss during COPY */
16141646
if (!CheckConnection())
16151647
return -1;

0 commit comments

Comments
 (0)