From 371cfa6bed37caca793fc5c2c8e45a087acc2b29 Mon Sep 17 00:00:00 2001
From: "Chao Li (Evan)" <lic@highgo.com>
Date: Fri, 12 Jun 2026 16:48:15 +0800
Subject: [PATCH v2] Fix psql pager selection for wrapped expanded output

psql decided whether to use the pager in expanded output before
calculating the data column width used by wrapped format. As a result,
wrapped expanded output could be counted without the extra lines added by
wrapping, so psql could print more than a screenful of output directly to
the terminal instead of invoking the pager.

Move the pager decision in print_aligned_vertical() until after the
wrapped data width is known. For wrapped vertical output, pass
count_table_lines() a per-column wrap-width array using that common data
width, since all expanded fields share the same data area width.

Add pager test coverage for wrapped expanded output.

Author: Chao Li <lic@highgo.com>
Reviewed-by: Erik Wienhold <ewie@ewie.name>
Discussion: https://postgr.es/m/A44110E7-6A03-4C67-95AD-527192A6C768@gmail.com
---
 src/bin/psql/t/030_pager.pl |  8 ++++++
 src/fe_utils/print.c        | 52 ++++++++++++++++++++++++-------------
 2 files changed, 42 insertions(+), 18 deletions(-)

diff --git a/src/bin/psql/t/030_pager.pl b/src/bin/psql/t/030_pager.pl
index d3f964639d3..a13c75227e3 100644
--- a/src/bin/psql/t/030_pager.pl
+++ b/src/bin/psql/t/030_pager.pl
@@ -130,6 +130,14 @@ do_command(
 	qr/55\r?$/m,
 	"execute command with footer that needs pagination");
 
+# With the 80-column pty, the payload wraps to two lines per row.
+# Expanded output also prints one record header per row, plus a trailing
+# blank line, so this produces 11 * 3 + 1 = 34 lines.
+do_command(
+	"\\pset expanded on\n\\pset format wrapped\nSELECT repeat('x',80) AS payload FROM generate_series(1,11);\n",
+	qr/34\r?$/m,
+	"execute SELECT query that needs pagination in expanded wrapped mode");
+
 # send psql an explicit \q to shut it down, else pty won't close properly
 $h->quit or die "psql returned $?";
 
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index bfbaf094d7e..5b03005c85e 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -1354,17 +1354,6 @@ print_aligned_vertical(const printTableContent *cont,
 		return;
 	}
 
-	/*
-	 * Deal with the pager here instead of in printTable(), because we could
-	 * get here via print_aligned_text() in expanded auto mode, and so we have
-	 * to recalculate the pager requirement based on vertical output.
-	 */
-	if (!is_pager)
-	{
-		IsPagerNeeded(cont, NULL, true, &fout, &is_pager);
-		is_local_pager = is_pager;
-	}
-
 	/* Find the maximum dimensions for the headers */
 	for (i = 0; i < cont->ncolumns; i++)
 	{
@@ -1415,13 +1404,6 @@ print_aligned_vertical(const printTableContent *cont,
 	dlineptr->ptr = pg_malloc(dformatsize);
 	hlineptr->ptr = pg_malloc(hformatsize);
 
-	if (cont->opt->start_table)
-	{
-		/* print title */
-		if (!opt_tuples_only && cont->title)
-			fprintf(fout, "%s\n", cont->title);
-	}
-
 	/*
 	 * Choose target output width: \pset columns, or $COLUMNS, or ioctl
 	 */
@@ -1569,6 +1551,40 @@ print_aligned_vertical(const printTableContent *cont,
 		dwidth = newdwidth;
 	}
 
+	/*
+	 * Deal with the pager here instead of in printTable(), because we could
+	 * get here via print_aligned_text() in expanded auto mode, and so we have
+	 * to recalculate the pager requirement based on vertical output.
+	 */
+	if (!is_pager)
+	{
+		unsigned int *width_wrap = NULL;
+
+		/*
+		 * Wrapping can add extra output lines that count_table_lines() can
+		 * only account for if it has wrap widths.  Vertical output uses the
+		 * same data width for every field, so use dwidth for every column.
+		 */
+		if (cont->opt->format == PRINT_WRAPPED && cont->ncolumns > 0)
+		{
+			width_wrap = pg_malloc_array(unsigned int, cont->ncolumns);
+			for (i = 0; i < cont->ncolumns; i++)
+				width_wrap[i] = dwidth;
+		}
+
+		IsPagerNeeded(cont, width_wrap, true, &fout, &is_pager);
+		is_local_pager = is_pager;
+
+		free(width_wrap);
+	}
+
+	if (cont->opt->start_table)
+	{
+		/* print title */
+		if (!opt_tuples_only && cont->title)
+			fprintf(fout, "%s\n", cont->title);
+	}
+
 	/* print records */
 	for (i = 0, ptr = cont->cells; *ptr; i++, ptr++)
 	{
-- 
2.50.1 (Apple Git-155)

