Fix behavior of printTable() and friends with externally-invoked pager.

The formatting modes that depend on knowledge of the terminal window width
did not work right when printing a query result that's been fetched in
sections (as a result of FETCH_SIZE).  ExecQueryUsingCursor() would force
use of the pager as soon as there's more than one result section, and then
print.c would see an output file pointer that's not stdout and incorrectly
conclude that the terminal window width isn't relevant.

This has been broken all along for non-expanded "wrapped" output format,
and as of 9.5 the issue affects expanded mode as well.  The problem also
caused "\pset expanded auto" mode to invariably *not* switch to expanded
output in a segmented result, which seems to me to be exactly backwards.

To fix, we need to pass down an "is_pager" flag to inform the print.c
subroutines that some calling level has already replaced stdout with a
pager pipe, so they should (a) not do that again and (b) nonetheless honor
the window size.  (Notably, this makes the first is_pager test in
print_aligned_text() not be dead code anymore.)

This patch is a bit invasive because there are so many existing calls of
printQuery()/printTable(), but fortunately all but a couple can just pass
"false" for the added parameter.

Back-patch to 9.5 but no further.  Given the lack of field complaints,
it's not clear that we should change the behavior in stable branches.
Also, the API change for printQuery()/printTable() might possibly break
third-party code, again something we don't like to do in stable branches.
However, it's not quite too late to do this in 9.5, and with the larger
scope of the problem there, it seems worth doing.
This commit is contained in:
Tom Lane 2015-12-02 18:20:33 -05:00
parent 1caef31d9e
commit d8ff060ecd
7 changed files with 102 additions and 72 deletions

View File

@ -9,6 +9,7 @@
#include "common.h"
#include <ctype.h>
#include <limits.h>
#include <signal.h>
#ifndef WIN32
#include <unistd.h> /* for write() */
@ -537,7 +538,7 @@ PSQLexecWatch(const char *query, const printQueryOpt *opt)
switch (PQresultStatus(res))
{
case PGRES_TUPLES_OK:
printQuery(res, opt, pset.queryFout, pset.logfile);
printQuery(res, opt, pset.queryFout, false, pset.logfile);
break;
case PGRES_COMMAND_OK:
@ -624,7 +625,7 @@ PrintQueryTuples(const PGresult *results)
return false;
}
printQuery(results, &my_popt, pset.queryFout, pset.logfile);
printQuery(results, &my_popt, pset.queryFout, false, pset.logfile);
/* close file/pipe, restore old setting */
setQFout(NULL);
@ -633,7 +634,7 @@ PrintQueryTuples(const PGresult *results)
pset.queryFoutPipe = queryFoutPipe_copy;
}
else
printQuery(results, &my_popt, pset.queryFout, pset.logfile);
printQuery(results, &my_popt, pset.queryFout, false, pset.logfile);
return true;
}
@ -1336,11 +1337,11 @@ ExecQueryUsingCursor(const char *query, double *elapsed_msec)
* If query requires multiple result sets, hack to ensure that
* only one pager instance is used for the whole mess
*/
pset.queryFout = PageOutput(100000, &(my_popt.topt));
pset.queryFout = PageOutput(INT_MAX, &(my_popt.topt));
did_pager = true;
}
printQuery(results, &my_popt, pset.queryFout, pset.logfile);
printQuery(results, &my_popt, pset.queryFout, did_pager, pset.logfile);
PQclear(results);

View File

@ -123,7 +123,7 @@ describeAggregates(const char *pattern, bool verbose, bool showSystem)
myopt.title = _("List of aggregate functions");
myopt.translate_header = true;
printQuery(res, &myopt, pset.queryFout, pset.logfile);
printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
PQclear(res);
return true;
@ -204,7 +204,7 @@ describeTablespaces(const char *pattern, bool verbose)
myopt.title = _("List of tablespaces");
myopt.translate_header = true;
printQuery(res, &myopt, pset.queryFout, pset.logfile);
printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
PQclear(res);
return true;
@ -469,7 +469,7 @@ describeFunctions(const char *functypes, const char *pattern, bool verbose, bool
myopt.translate_columns = translate_columns;
myopt.n_translate_columns = lengthof(translate_columns);
printQuery(res, &myopt, pset.queryFout, pset.logfile);
printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
PQclear(res);
return true;
@ -585,7 +585,7 @@ describeTypes(const char *pattern, bool verbose, bool showSystem)
myopt.title = _("List of data types");
myopt.translate_header = true;
printQuery(res, &myopt, pset.queryFout, pset.logfile);
printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
PQclear(res);
return true;
@ -660,7 +660,7 @@ describeOperators(const char *pattern, bool verbose, bool showSystem)
myopt.title = _("List of operators");
myopt.translate_header = true;
printQuery(res, &myopt, pset.queryFout, pset.logfile);
printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
PQclear(res);
return true;
@ -731,7 +731,7 @@ listAllDbs(const char *pattern, bool verbose)
myopt.title = _("List of databases");
myopt.translate_header = true;
printQuery(res, &myopt, pset.queryFout, pset.logfile);
printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
PQclear(res);
return true;
@ -847,7 +847,7 @@ permissionsList(const char *pattern)
myopt.translate_columns = translate_columns;
myopt.n_translate_columns = lengthof(translate_columns);
printQuery(res, &myopt, pset.queryFout, pset.logfile);
printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
termPQExpBuffer(&buf);
PQclear(res);
@ -921,7 +921,7 @@ listDefaultACLs(const char *pattern)
myopt.translate_columns = translate_columns;
myopt.n_translate_columns = lengthof(translate_columns);
printQuery(res, &myopt, pset.queryFout, pset.logfile);
printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
termPQExpBuffer(&buf);
PQclear(res);
@ -1117,7 +1117,7 @@ objectDescription(const char *pattern, bool showSystem)
myopt.translate_columns = translate_columns;
myopt.n_translate_columns = lengthof(translate_columns);
printQuery(res, &myopt, pset.queryFout, pset.logfile);
printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
PQclear(res);
return true;
@ -1260,7 +1260,7 @@ describeOneTableDetails(const char *schemaname,
{
printfPQExpBuffer(&buf,
"SELECT c.relchecks, c.relkind, c.relhasindex, c.relhasrules, "
"c.relhastriggers, c.relrowsecurity, c.relforcerowsecurity, "
"c.relhastriggers, c.relrowsecurity, c.relforcerowsecurity, "
"c.relhasoids, %s, c.reltablespace, "
"CASE WHEN c.reloftype = 0 THEN '' ELSE c.reloftype::pg_catalog.regtype::pg_catalog.text END, "
"c.relpersistence, c.relreplident\n"
@ -2546,8 +2546,7 @@ describeOneTableDetails(const char *schemaname,
printTableAddFooter(&cont, buf.data);
}
printTable(&cont, pset.queryFout, pset.logfile);
printTableCleanup(&cont);
printTable(&cont, pset.queryFout, false, pset.logfile);
retval = true;
@ -2791,7 +2790,7 @@ describeRoles(const char *pattern, bool verbose)
}
termPQExpBuffer(&buf);
printTable(&cont, pset.queryFout, pset.logfile);
printTable(&cont, pset.queryFout, false, pset.logfile);
printTableCleanup(&cont);
for (i = 0; i < nrows; i++)
@ -2865,7 +2864,7 @@ listDbRoleSettings(const char *pattern, const char *pattern2)
myopt.title = _("List of settings");
myopt.translate_header = true;
printQuery(res, &myopt, pset.queryFout, pset.logfile);
printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
}
PQclear(res);
@ -3029,7 +3028,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
myopt.translate_columns = translate_columns;
myopt.n_translate_columns = lengthof(translate_columns);
printQuery(res, &myopt, pset.queryFout, pset.logfile);
printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
}
PQclear(res);
@ -3105,7 +3104,7 @@ listLanguages(const char *pattern, bool verbose, bool showSystem)
myopt.title = _("List of languages");
myopt.translate_header = true;
printQuery(res, &myopt, pset.queryFout, pset.logfile);
printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
PQclear(res);
return true;
@ -3192,7 +3191,7 @@ listDomains(const char *pattern, bool verbose, bool showSystem)
myopt.title = _("List of domains");
myopt.translate_header = true;
printQuery(res, &myopt, pset.queryFout, pset.logfile);
printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
PQclear(res);
return true;
@ -3268,7 +3267,7 @@ listConversions(const char *pattern, bool verbose, bool showSystem)
myopt.translate_columns = translate_columns;
myopt.n_translate_columns = lengthof(translate_columns);
printQuery(res, &myopt, pset.queryFout, pset.logfile);
printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
PQclear(res);
return true;
@ -3334,7 +3333,7 @@ listEventTriggers(const char *pattern, bool verbose)
myopt.translate_columns = translate_columns;
myopt.n_translate_columns = lengthof(translate_columns);
printQuery(res, &myopt, pset.queryFout, pset.logfile);
printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
PQclear(res);
return true;
@ -3433,7 +3432,7 @@ listCasts(const char *pattern, bool verbose)
myopt.translate_columns = translate_columns;
myopt.n_translate_columns = lengthof(translate_columns);
printQuery(res, &myopt, pset.queryFout, pset.logfile);
printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
PQclear(res);
return true;
@ -3509,7 +3508,7 @@ listCollations(const char *pattern, bool verbose, bool showSystem)
myopt.translate_columns = translate_columns;
myopt.n_translate_columns = lengthof(translate_columns);
printQuery(res, &myopt, pset.queryFout, pset.logfile);
printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
PQclear(res);
return true;
@ -3566,7 +3565,7 @@ listSchemas(const char *pattern, bool verbose, bool showSystem)
myopt.title = _("List of schemas");
myopt.translate_header = true;
printQuery(res, &myopt, pset.queryFout, pset.logfile);
printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
PQclear(res);
return true;
@ -3623,7 +3622,7 @@ listTSParsers(const char *pattern, bool verbose)
myopt.title = _("List of text search parsers");
myopt.translate_header = true;
printQuery(res, &myopt, pset.queryFout, pset.logfile);
printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
PQclear(res);
return true;
@ -3769,7 +3768,7 @@ describeOneTSParser(const char *oid, const char *nspname, const char *prsname)
myopt.translate_columns = translate_columns;
myopt.n_translate_columns = lengthof(translate_columns);
printQuery(res, &myopt, pset.queryFout, pset.logfile);
printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
PQclear(res);
@ -3801,7 +3800,7 @@ describeOneTSParser(const char *oid, const char *nspname, const char *prsname)
myopt.translate_columns = NULL;
myopt.n_translate_columns = 0;
printQuery(res, &myopt, pset.queryFout, pset.logfile);
printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
PQclear(res);
return true;
@ -3869,7 +3868,7 @@ listTSDictionaries(const char *pattern, bool verbose)
myopt.title = _("List of text search dictionaries");
myopt.translate_header = true;
printQuery(res, &myopt, pset.queryFout, pset.logfile);
printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
PQclear(res);
return true;
@ -3937,7 +3936,7 @@ listTSTemplates(const char *pattern, bool verbose)
myopt.title = _("List of text search templates");
myopt.translate_header = true;
printQuery(res, &myopt, pset.queryFout, pset.logfile);
printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
PQclear(res);
return true;
@ -3994,7 +3993,7 @@ listTSConfigs(const char *pattern, bool verbose)
myopt.title = _("List of text search configurations");
myopt.translate_header = true;
printQuery(res, &myopt, pset.queryFout, pset.logfile);
printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
PQclear(res);
return true;
@ -4132,7 +4131,7 @@ describeOneTSConfig(const char *oid, const char *nspname, const char *cfgname,
myopt.topt.default_footer = false;
myopt.translate_header = true;
printQuery(res, &myopt, pset.queryFout, pset.logfile);
printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
termPQExpBuffer(&title);
@ -4215,7 +4214,7 @@ listForeignDataWrappers(const char *pattern, bool verbose)
myopt.title = _("List of foreign-data wrappers");
myopt.translate_header = true;
printQuery(res, &myopt, pset.queryFout, pset.logfile);
printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
PQclear(res);
return true;
@ -4294,7 +4293,7 @@ listForeignServers(const char *pattern, bool verbose)
myopt.title = _("List of foreign servers");
myopt.translate_header = true;
printQuery(res, &myopt, pset.queryFout, pset.logfile);
printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
PQclear(res);
return true;
@ -4352,7 +4351,7 @@ listUserMappings(const char *pattern, bool verbose)
myopt.title = _("List of user mappings");
myopt.translate_header = true;
printQuery(res, &myopt, pset.queryFout, pset.logfile);
printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
PQclear(res);
return true;
@ -4426,7 +4425,7 @@ listForeignTables(const char *pattern, bool verbose)
myopt.title = _("List of foreign tables");
myopt.translate_header = true;
printQuery(res, &myopt, pset.queryFout, pset.logfile);
printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
PQclear(res);
return true;
@ -4480,7 +4479,7 @@ listExtensions(const char *pattern)
myopt.title = _("List of installed extensions");
myopt.translate_header = true;
printQuery(res, &myopt, pset.queryFout, pset.logfile);
printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
PQclear(res);
return true;
@ -4587,7 +4586,7 @@ listOneExtensionContents(const char *extname, const char *oid)
myopt.title = title;
myopt.translate_header = true;
printQuery(res, &myopt, pset.queryFout, pset.logfile);
printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
PQclear(res);
return true;

View File

@ -308,7 +308,7 @@ do_lo_list(void)
myopt.title = _("Large objects");
myopt.translate_header = true;
printQuery(res, &myopt, pset.queryFout, pset.logfile);
printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
PQclear(res);
return true;

View File

@ -191,10 +191,11 @@ const unicodeStyleFormat unicode_style = {
/* Local functions */
static int strlen_max_width(unsigned char *str, int *target_width, int encoding);
static void IsPagerNeeded(const printTableContent *cont, const int extra_lines, bool expanded,
static void IsPagerNeeded(const printTableContent *cont, int extra_lines, bool expanded,
FILE **fout, bool *is_pager);
static void print_aligned_vertical(const printTableContent *cont, FILE *fout);
static void print_aligned_vertical(const printTableContent *cont,
FILE *fout, bool is_pager);
/* Count number of digits in integral part of number */
@ -570,7 +571,7 @@ _print_horizontal_line(const unsigned int ncolumns, const unsigned int *widths,
* Print pretty boxes around cells.
*/
static void
print_aligned_text(const printTableContent *cont, FILE *fout)
print_aligned_text(const printTableContent *cont, FILE *fout, bool is_pager)
{
bool opt_tuples_only = cont->opt->tuples_only;
int encoding = cont->opt->encoding;
@ -605,7 +606,7 @@ print_aligned_text(const printTableContent *cont, FILE *fout)
int *bytes_output; /* Bytes output for column value */
printTextLineWrap *wrap; /* Wrap status for each column */
int output_columns = 0; /* Width of interactive console */
bool is_pager = false;
bool is_local_pager = false;
if (cancel_pressed)
return;
@ -813,7 +814,7 @@ print_aligned_text(const printTableContent *cont, FILE *fout)
if (cont->opt->expanded == 2 && output_columns > 0 &&
(output_columns < total_header_width || output_columns < width_total))
{
print_aligned_vertical(cont, fout);
print_aligned_vertical(cont, fout, is_pager);
goto cleanup;
}
@ -822,11 +823,11 @@ print_aligned_text(const printTableContent *cont, FILE *fout)
(output_columns < total_header_width || output_columns < width_total))
{
fout = PageOutput(INT_MAX, cont->opt); /* force pager */
is_pager = true;
is_pager = is_local_pager = true;
}
/* Check if newlines or our wrapping now need the pager */
if (!is_pager)
if (!is_pager && fout == stdout)
{
/* scan all cells, find maximum width, compute cell_count */
for (i = 0, ptr = cont->cells; *ptr; ptr++, cell_count++)
@ -862,6 +863,7 @@ print_aligned_text(const printTableContent *cont, FILE *fout)
}
}
IsPagerNeeded(cont, extra_output_lines, false, &fout, &is_pager);
is_local_pager = is_pager;
}
/* time to output */
@ -1153,7 +1155,7 @@ cleanup:
free(bytes_output);
free(wrap);
if (is_pager)
if (is_local_pager)
ClosePager(fout);
}
@ -1215,7 +1217,8 @@ print_aligned_vertical_line(const printTextFormat *format,
}
static void
print_aligned_vertical(const printTableContent *cont, FILE *fout)
print_aligned_vertical(const printTableContent *cont,
FILE *fout, bool is_pager)
{
bool opt_tuples_only = cont->opt->tuples_only;
unsigned short opt_border = cont->opt->border;
@ -1233,7 +1236,7 @@ print_aligned_vertical(const printTableContent *cont, FILE *fout)
dformatsize = 0;
struct lineptr *hlineptr,
*dlineptr;
bool is_pager = false,
bool is_local_pager = false,
hmultiline = false,
dmultiline = false;
int output_columns = 0; /* Width of interactive console */
@ -1267,7 +1270,11 @@ print_aligned_vertical(const printTableContent *cont, FILE *fout)
* get here via print_aligned_text() in expanded auto mode, and so we have
* to recalculate the pager requirement based on vertical output.
*/
IsPagerNeeded(cont, 0, true, &fout, &is_pager);
if (!is_pager)
{
IsPagerNeeded(cont, 0, true, &fout, &is_pager);
is_local_pager = is_pager;
}
/* Find the maximum dimensions for the headers */
for (i = 0; i < cont->ncolumns; i++)
@ -1714,7 +1721,7 @@ print_aligned_vertical(const printTableContent *cont, FILE *fout)
free(hlineptr);
free(dlineptr);
if (is_pager)
if (is_local_pager)
ClosePager(fout);
}
@ -3075,8 +3082,8 @@ printTableCleanup(printTableContent *const content)
* Setup pager if required
*/
static void
IsPagerNeeded(const printTableContent *cont, const int extra_lines, bool expanded, FILE **fout,
bool *is_pager)
IsPagerNeeded(const printTableContent *cont, int extra_lines, bool expanded,
FILE **fout, bool *is_pager)
{
if (*fout == stdout)
{
@ -3107,12 +3114,18 @@ IsPagerNeeded(const printTableContent *cont, const int extra_lines, bool expande
}
/*
* Use this to print just any table in the supported formats.
* Use this to print any table in the supported formats.
*
* cont: table data and formatting options
* fout: where to print to
* is_pager: true if caller has already redirected fout to be a pager pipe
* flog: if not null, also print the table there (for --log-file option)
*/
void
printTable(const printTableContent *cont, FILE *fout, FILE *flog)
printTable(const printTableContent *cont,
FILE *fout, bool is_pager, FILE *flog)
{
bool is_pager = false;
bool is_local_pager = false;
if (cancel_pressed)
return;
@ -3120,15 +3133,19 @@ printTable(const printTableContent *cont, FILE *fout, FILE *flog)
if (cont->opt->format == PRINT_NOTHING)
return;
/* print_aligned_*() handles the pager themselves */
if (cont->opt->format != PRINT_ALIGNED &&
/* print_aligned_*() handle the pager themselves */
if (!is_pager &&
cont->opt->format != PRINT_ALIGNED &&
cont->opt->format != PRINT_WRAPPED)
{
IsPagerNeeded(cont, 0, (cont->opt->expanded == 1), &fout, &is_pager);
is_local_pager = is_pager;
}
/* print the stuff */
if (flog)
print_aligned_text(cont, flog);
print_aligned_text(cont, flog, false);
switch (cont->opt->format)
{
@ -3140,10 +3157,17 @@ printTable(const printTableContent *cont, FILE *fout, FILE *flog)
break;
case PRINT_ALIGNED:
case PRINT_WRAPPED:
if (cont->opt->expanded == 1)
print_aligned_vertical(cont, fout);
/*
* In expanded-auto mode, force vertical if a pager is passed in;
* else we may make different decisions for different hunks of the
* query result.
*/
if (cont->opt->expanded == 1 ||
(cont->opt->expanded == 2 && is_pager))
print_aligned_vertical(cont, fout, is_pager);
else
print_aligned_text(cont, fout);
print_aligned_text(cont, fout, is_pager);
break;
case PRINT_HTML:
if (cont->opt->expanded == 1)
@ -3181,17 +3205,22 @@ printTable(const printTableContent *cont, FILE *fout, FILE *flog)
exit(EXIT_FAILURE);
}
if (is_pager)
if (is_local_pager)
ClosePager(fout);
}
/*
* Use this to print query results
*
* It calls printTable with all the things set straight.
* result: result of a successful query
* opt: formatting options
* fout: where to print to
* is_pager: true if caller has already redirected fout to be a pager pipe
* flog: if not null, also print the data there (for --log-file option)
*/
void
printQuery(const PGresult *result, const printQueryOpt *opt, FILE *fout, FILE *flog)
printQuery(const PGresult *result, const printQueryOpt *opt,
FILE *fout, bool is_pager, FILE *flog)
{
printTableContent cont;
int i,
@ -3271,7 +3300,7 @@ printQuery(const PGresult *result, const printQueryOpt *opt, FILE *fout, FILE *f
printTableAddFooter(&cont, *footer);
}
printTable(&cont, fout, flog);
printTable(&cont, fout, is_pager, flog);
printTableCleanup(&cont);
}

View File

@ -184,9 +184,10 @@ extern void printTableAddFooter(printTableContent *const content,
extern void printTableSetFooter(printTableContent *const content,
const char *footer);
extern void printTableCleanup(printTableContent *const content);
extern void printTable(const printTableContent *cont, FILE *fout, FILE *flog);
extern void printTable(const printTableContent *cont,
FILE *fout, bool is_pager, FILE *flog);
extern void printQuery(const PGresult *result, const printQueryOpt *opt,
FILE *fout, FILE *flog);
FILE *fout, bool is_pager, FILE *flog);
extern void setDecimalLocale(void);
extern const printTextFormat *get_line_style(const printTableOpt *opt);

View File

@ -162,7 +162,7 @@ main(int argc, char *argv[])
popt.translate_columns = translate_columns;
popt.n_translate_columns = lengthof(translate_columns);
printQuery(result, &popt, stdout, NULL);
printQuery(result, &popt, stdout, false, NULL);
PQfinish(conn);
exit(0);

View File

@ -161,7 +161,7 @@ main(int argc, char *argv[])
popt.translate_columns = translate_columns;
popt.n_translate_columns = lengthof(translate_columns);
printQuery(result, &popt, stdout, NULL);
printQuery(result, &popt, stdout, false, NULL);
PQfinish(conn);
exit(0);