v4-0001-Improve-psql-s-ability-to-select-pager-mode-accur.patch
text/x-diff
Filename: v4-0001-Improve-psql-s-ability-to-select-pager-mode-accur.patch
Type: text/x-diff
Part: 0
Patch
Same data as JSON:
GET /api/v1/attachments/:id/patch
the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes.
API reference →
Format: format-patch
Series: patch v4-0001
Subject: Improve psql's ability to select pager mode accurately.
| File | + | − |
|---|---|---|
| src/fe_utils/print.c | 138 | 61 |
From 0b6780211acafa3504b47692f87b24f891dbefe0 Mon Sep 17 00:00:00 2001
From: Tom Lane <tgl@sss.pgh.pa.us>
Date: Wed, 1 Oct 2025 17:25:00 -0400
Subject: [PATCH v4 1/2] Improve psql's ability to select pager mode
accurately.
The code in print.c that's concerned with counting the number of lines
that will be needed missed a lot of edge cases:
* While plain aligned mode accounted for embedded newlines in column
headers and table cells, unaligned and expanded output modes did not.
* In particular, since expanded mode repeats the headers for each
record, we need to account for embedded newlines in the headers for
each record.
* Multi-line table titles were not accounted for.
* tuples_only mode (where headers aren't printed) wasn't accounted
for.
* Footers were accounted for as one line per footer, again missing
the possibility of multi-line footers. (In some cases such as
"\d+" on a view, there can be many lines in a footer.)
To fix, move the entire responsibility for counting lines into
IsPagerNeeded (or actually, into a new subroutine count_table_lines),
and then expand the logic as appropriate. Also restructure to make
it perhaps a bit easier to follow.
Author: Erik Wienhold <ewie@ewie.name>
Reviewed-by: Tom Lane <tgl@sss.pgh.pa.us>
Discussion: https://postgr.es/m/2dd2430f-dd20-4c89-97fd-242616a3d768@ewie.name
---
src/fe_utils/print.c | 199 ++++++++++++++++++++++++++++++-------------
1 file changed, 138 insertions(+), 61 deletions(-)
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 4af0f32f2fc..71bc14d499b 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -266,8 +266,13 @@ static 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, int extra_lines, bool expanded,
+static void IsPagerNeeded(const printTableContent *cont,
+ const unsigned int *width_wrap,
+ bool expanded,
FILE **fout, bool *is_pager);
+static int count_table_lines(const printTableContent *cont,
+ const unsigned int *width_wrap,
+ bool expanded);
static void print_aligned_vertical(const printTableContent *cont,
FILE *fout, bool is_pager);
@@ -656,8 +661,6 @@ print_aligned_text(const printTableContent *cont, FILE *fout, bool is_pager)
unsigned char **format_buf;
unsigned int width_total;
unsigned int total_header_width;
- unsigned int extra_row_output_lines = 0;
- unsigned int extra_output_lines = 0;
const char *const *ptr;
@@ -722,14 +725,9 @@ print_aligned_text(const printTableContent *cont, FILE *fout, bool is_pager)
max_nl_lines[i] = nl_lines;
if (bytes_required > max_bytes[i])
max_bytes[i] = bytes_required;
- if (nl_lines > extra_row_output_lines)
- extra_row_output_lines = nl_lines;
width_header[i] = width;
}
- /* Add height of tallest header column */
- extra_output_lines += extra_row_output_lines;
- extra_row_output_lines = 0;
/* scan all cells, find maximum width, compute cell_count */
for (i = 0, ptr = cont->cells; *ptr; ptr++, i++, cell_count++)
@@ -889,43 +887,10 @@ print_aligned_text(const printTableContent *cont, FILE *fout, bool is_pager)
is_pager = is_local_pager = true;
}
- /* Check if newlines or our wrapping now need the pager */
- if (!is_pager && fout == stdout)
+ /* Check if there are enough lines to require the pager */
+ if (!is_pager)
{
- /* scan all cells, find maximum width, compute cell_count */
- for (i = 0, ptr = cont->cells; *ptr; ptr++, cell_count++)
- {
- int width,
- nl_lines,
- bytes_required;
-
- pg_wcssize((const unsigned char *) *ptr, strlen(*ptr), encoding,
- &width, &nl_lines, &bytes_required);
-
- /*
- * A row can have both wrapping and newlines that cause it to
- * display across multiple lines. We check for both cases below.
- */
- if (width > 0 && width_wrap[i])
- {
- unsigned int extra_lines;
-
- /* don't count the first line of nl_lines - it's not "extra" */
- extra_lines = ((width - 1) / width_wrap[i]) + nl_lines - 1;
- if (extra_lines > extra_row_output_lines)
- extra_row_output_lines = extra_lines;
- }
-
- /* i is the current column number: increment with wrap */
- if (++i >= col_count)
- {
- i = 0;
- /* At last column of each row, add tallest column height */
- extra_output_lines += extra_row_output_lines;
- extra_row_output_lines = 0;
- }
- }
- IsPagerNeeded(cont, extra_output_lines, false, &fout, &is_pager);
+ IsPagerNeeded(cont, width_wrap, false, &fout, &is_pager);
is_local_pager = is_pager;
}
@@ -1376,7 +1341,7 @@ print_aligned_vertical(const printTableContent *cont,
*/
if (!is_pager)
{
- IsPagerNeeded(cont, 0, true, &fout, &is_pager);
+ IsPagerNeeded(cont, NULL, true, &fout, &is_pager);
is_local_pager = is_pager;
}
@@ -3398,37 +3363,149 @@ printTableCleanup(printTableContent *const content)
* IsPagerNeeded
*
* Setup pager if required
+ *
+ * cont: table data to be printed
+ * width_wrap[]: per-column maximum width, or NULL if caller will not wrap
+ * expanded: expanded mode?
+ * fout: where to print to (in/out argument)
+ * is_pager: output argument
+ *
+ * If we decide pager is needed, *fout is modified and *is_pager is set true
*/
static void
-IsPagerNeeded(const printTableContent *cont, int extra_lines, bool expanded,
+IsPagerNeeded(const printTableContent *cont, const unsigned int *width_wrap,
+ bool expanded,
FILE **fout, bool *is_pager)
{
if (*fout == stdout)
{
int lines;
+ lines = count_table_lines(cont, width_wrap, expanded);
+
+ *fout = PageOutput(lines, cont->opt);
+ *is_pager = (*fout != stdout);
+ }
+ else
+ *is_pager = false;
+}
+
+/*
+ * Count the number of lines needed to print the given table.
+ *
+ * cont: table data to be printed
+ * width_wrap[]: per-column maximum width, or NULL if caller will not wrap
+ * expanded: expanded mode?
+ *
+ * We currently handle only regular and expanded modes here.
+ */
+static int
+count_table_lines(const printTableContent *cont,
+ const unsigned int *width_wrap,
+ bool expanded)
+{
+ int *header_height;
+ int lines,
+ max_lines = 0,
+ nl_lines,
+ i;
+ int encoding = cont->opt->encoding;
+ const char *const *cell;
+
+ /*
+ * Scan all column headers and determine their heights. Cache the values
+ * since expanded mode repeats the headers for every record.
+ */
+ header_height = (int *) pg_malloc(cont->ncolumns * sizeof(int));
+ for (i = 0; i < cont->ncolumns; i++)
+ {
+ pg_wcssize((const unsigned char *) cont->headers[i],
+ strlen(cont->headers[i]), encoding,
+ NULL, &header_height[i], NULL);
+ }
+
+ /*
+ * Expanded mode writes one separator line per record. Normal mode writes
+ * a single separator line between header and rows.
+ */
+ lines = expanded ? cont->nrows : 1;
+
+ /* Scan all cells to count their lines */
+ for (i = 0, cell = cont->cells; *cell; cell++)
+ {
+ int width;
+
+ /* Count the original line breaks */
+ pg_wcssize((const unsigned char *) *cell, strlen(*cell), encoding,
+ &width, &nl_lines, NULL);
+
+ /* Count extra lines due to wrapping */
+ if (width > 0 && width_wrap && width_wrap[i])
+ nl_lines += (width - 1) / width_wrap[i];
+
if (expanded)
- lines = (cont->ncolumns + 1) * cont->nrows;
+ {
+ /* Pick the height of the header or cell, whichever is taller */
+ if (nl_lines > header_height[i])
+ lines += nl_lines;
+ else
+ lines += header_height[i];
+ }
else
- lines = cont->nrows + 1;
+ {
+ /* Remember max height in the current row */
+ if (nl_lines > max_lines)
+ max_lines = nl_lines;
+ }
- if (!cont->opt->tuples_only)
+ /* i is the current column number: increment with wrap */
+ if (++i >= cont->ncolumns)
{
- printTableFooter *f;
+ i = 0;
+ if (!expanded)
+ {
+ /* At last column of each row, add tallest column height */
+ lines += max_lines;
+ max_lines = 0;
+ }
+ }
+ }
- /*
- * FIXME -- this is slightly bogus: it counts the number of
- * footers, not the number of lines in them.
- */
- for (f = cont->footers; f; f = f->next)
- lines++;
+ /* Account for header and footer decoration */
+ if (!cont->opt->tuples_only)
+ {
+ if (cont->title)
+ {
+ /* Add height of title */
+ pg_wcssize((const unsigned char *) cont->title, strlen(cont->title),
+ encoding, NULL, &nl_lines, NULL);
+ lines += nl_lines;
}
- *fout = PageOutput(lines + extra_lines, cont->opt);
- *is_pager = (*fout != stdout);
+ if (!expanded)
+ {
+ /* Add height of tallest header column */
+ max_lines = 0;
+ for (i = 0; i < cont->ncolumns; i++)
+ {
+ if (header_height[i] > max_lines)
+ max_lines = header_height[i];
+ }
+ lines += max_lines;
+ }
+
+ /* Add all footer lines */
+ for (printTableFooter *f = cont->footers; f; f = f->next)
+ {
+ pg_wcssize((const unsigned char *) f->data, strlen(f->data),
+ encoding, NULL, &nl_lines, NULL);
+ lines += nl_lines;
+ }
}
- else
- *is_pager = false;
+
+ free(header_height);
+
+ return lines;
}
/*
@@ -3456,7 +3533,7 @@ printTable(const printTableContent *cont,
cont->opt->format != PRINT_ALIGNED &&
cont->opt->format != PRINT_WRAPPED)
{
- IsPagerNeeded(cont, 0, (cont->opt->expanded == 1), &fout, &is_pager);
+ IsPagerNeeded(cont, NULL, (cont->opt->expanded == 1), &fout, &is_pager);
is_local_pager = is_pager;
}
--
2.43.7